Project Home (DESIGN-REVIEW §B2): the project is promoted from a 280px
sidebar card to a first-class main-area view. ProjectCard.tsx (1,257
lines) is replaced by a select-only ProjectRow plus tabs for Overview,
Sessions, Automation, Config and Files. The PortMappings, FileManager
and ContainerProgress modals are absorbed rather than reimplemented.
Config gains a Saved/Saving/Failed indicator — save-on-blur failures
previously reached only console.error.
Tier-1 polish (DESIGN-REVIEW §A): new elevation, muted-accent, disabled
and focus-ring tokens; a global :focus-visible ring with every
focus:outline-none removed; filled buttons moved to --accent-emphasis
and white-on-success toggles retired, fixing three WCAG AA failures
(2.1:1, 2.5:1, 2.4:1); a shared Modal primitive with role="dialog",
focus trap and restore, adopted by all remaining modals; status
indicators that carry a glyph and word rather than colour alone.
Ctrl+Shift+W closes a tab, deliberately not Ctrl+W — that is readline's
kill-word, used constantly in the terminal this app is built around.
Auth Bridge: a general loopback-callback bridge so browser logins run
inside a container (aws sso login, Concourse fly login, claude login)
can complete against the host browser. Listeners are discovered from
/proc/net/tcp{,6} — ss/netstat/lsof are absent from the image — bound on
host 127.0.0.1 only, and tunnelled in over the Docker API via socat,
which keeps working on Docker Desktop where container IPs are not
routable. Falls back to [::1] because Node resolves localhost to IPv6
first, so claude login often binds ::1 alone. Opt-in per project.
This extracts create_attached_exec() and moves the existing terminal
session path onto it, so there is one attached-exec implementation
rather than two.
Shared auth token: `claude setup-token` is run in a container, the token
is stored in the OS keychain and injected as CLAUDE_CODE_OAUTH_TOKEN
into Anthropic-backend projects. Contrary to the initial design note,
setup-token uses an Anthropic-hosted redirect and blocks on a stdin
paste prompt rather than a loopback callback, so a stdin command is
required for the flow to complete.
The token is never logged, never returned to the frontend, and is
redacted from the streamed output with a stateful matcher that withholds
any tail that could still grow into a secret. Change detection uses a
random rotation id rather than a hash, since a hash in a docker-inspect
readable label would be an offline verification oracle.
Frontend 33 -> 51 tests; Rust 34 tests. Both builds clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- F2: upload_host_file_to_container now reads the dropped file into a Vec
inside the blocking task and sizes the tar entry from those exact bytes,
rather than stat-then-stream where a file changing size between the
stat and the read could desync the tar header and silently corrupt the
archive. Still runs off the async worker; memory stays bounded by the
256 MiB drop cap.
- F4: the "Backup saved" confirmation now auto-clears after 8s (guarded
against clobbering a newer status message) instead of lingering in the
project card's status line indefinitely.
F1 (claimed AWS CLI regression from empty-env neutralization) was a false
positive: verified against aws-cli 2.35 that an empty AWS_ACCESS_KEY_ID is
treated as absent and botocore falls through to ~/.aws/credentials (the
call reached AWS and returned InvalidClientTokenId for the file's key, not
PartialCredentialsError). No change needed.
cargo check / tsc / vitest all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- L-e: route terminal file drops purely by a bounds hit-test instead of
the `active` flag. Inactive panes are display:none (zero-size rect) so
they never match; a zero-size guard makes that explicit. Correct for
the current tabbed layout and future-proof for split panes, where a
drop on a visible-but-unfocused pane previously matched no handler.
- L-f: stream the dropped file straight into the upload tar inside a
blocking task (new exec::upload_host_file_to_container) instead of
reading the whole file into a Vec and then re-packing it. Peak memory
drops from ~2x to ~1x the file size, and the synchronous file IO no
longer runs on the async worker.
cargo check / tsc / vitest all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- M1 (race): don't mount the host AWS dir for static-credential Bedrock.
sync_bedrock_credentials() is the sole writer of ~/.aws/credentials in
that mode, and mounting /tmp/.host-aws let the entrypoint's
`rm -rf ~/.aws; cp -a` race that write at startup (only when a global
aws_config_path was also set). Static keys + AWS_REGION env are
self-sufficient and don't need the host config, so skipping the mount
removes the dual-writer entirely.
- L-a: exit codes are now read via wait_for_exec_exit(), which polls
inspect_exec until the exec reports finished, so a non-zero tar/cred
exit isn't missed by reading exit_code too early. The backup only fails
on a definitively non-zero code (falls back to the empty-output check
if undeterminable).
- L-b: fixed two comments that referenced the old
write_bedrock_static_credentials name (now sync_bedrock_credentials).
- L-c: entrypoint only rewrites ~/.claude.json when awsAuthRefresh is
actually present, avoiding a needless jq reformat on every non-SSO
start.
- L-d: backup script traps EXIT to remove its mktemp staging dir even
when tar fails, so failed backups don't accumulate temp dirs (with the
sanitized config copy) in the container.
L-e (drop routing) is a non-issue: the layout is tabbed, so only one
terminal pane is ever visible; the active-guard routing is correct.
Verified the race fix, trap cleanup, grep guard, and exit-code polling.
cargo check / tsc / vitest all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes from the code review of this branch:
- Backup requires a running container (it runs via `docker exec`, which
can't run on a stopped one). Removed the misleading "Backup" button
from the stopped-project actions, added an explicit running check with
a clear error, and corrected the doc comment. (H1)
- jq sanitization fallback no longer leaks secrets: if ~/.claude.json
can't be parsed, the backup substitutes an empty object and warns to
stderr instead of copying the raw file (which held primaryApiKey /
oauthAccount). Verified the raw key never reaches the archive. (H2)
- Dropped-file paths typed into the terminal are now always single-quoted
(with '\'' escaping), not only when they contain whitespace — a name
like `foo$(whoami).txt` was previously sent raw into the shell. (M2)
- write_bedrock_static_credentials checks the exec exit code via the new
exec_oneshot_env_status and fails loudly on a write/chmod error instead
of silently reporting success. exec_oneshot keeps its
ignore-exit-code behavior so list_container_files is unaffected. (M4)
- Backup removes a partial/truncated archive on any stream error and
treats a non-zero tar exit code as failure (a truncated gzip was
previously reported as success). (L1)
- Dropped files are capped at 256 MiB to avoid ballooning host RAM
(the file is read fully into memory then re-tarred). (M3)
- Stopped excluding .git/objects from the backup so git history,
including unpushed commits, is preserved faithfully. (L3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a project switched backends (e.g. Bedrock -> Anthropic), the
recreated container kept authenticating against Bedrock, and SSO kept
firing after switching away. Three root causes, all fixed:
1. Recreation builds the new container from a `docker commit` snapshot.
commit always bakes the previous container's full ENV into the image
(an empty commit Config does NOT strip it, and the commit API cannot
remove env). So CLAUDE_CODE_USE_BEDROCK=1 / AWS_* survived into the
new container. Fix: create_container now explicitly clears every
managed auth key the active backend does not set (MANAGED_AUTH_KEYS),
so create-time env overrides the stale baked-in values.
2. awsAuthRefresh was written into ~/.claude.json (persisted home
volume) and never removed, so Claude Code kept invoking
triple-c-sso-refresh after switching to a non-SSO backend. Fix:
entrypoint now deletes awsAuthRefresh when AWS_SSO_AUTH_REFRESH_CMD
is unset, idempotent both ways.
3. Static/session AWS creds were baked into Config.Env at create time,
so a stop/start kept stale creds and rotated keys never refreshed
without a full recreation. Fix: static creds are no longer injected
as env vars; write_bedrock_static_credentials() writes
~/.aws/credentials (0600, secrets via exec env not argv) on every
start, and removes a stale ~/.aws/config left from a prior profile/SSO
session. Static creds also dropped from the bedrock fingerprint so a
key rotation refreshes in place instead of forcing recreation.
Adds exec_oneshot_env() for env-carrying one-shot execs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove unused `any_docker_mcp()` function, add `#[allow(unused_imports)]`
and `#[allow(dead_code)]` annotations to suppress false-positive warnings.
Update README.md and HOW-TO-USE.md with Ollama and LiteLLM auth backend
documentation including best-effort compatibility notices.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds two new features for running project containers:
1. Bash Shell Tab: A "Shell" button on running projects opens a plain
bash -l session instead of Claude Code, useful for direct container
inspection, package installation, and debugging. Tab labels show
"(bash)" suffix to distinguish from Claude sessions.
2. File Manager: A "Files" button opens a modal file browser for
navigating container directories, downloading files to the host,
and uploading files from the host. Supports breadcrumb navigation
and works with any path including those outside mounted projects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enables Claude Code's /voice command inside Docker containers by
capturing microphone audio in the Tauri webview and streaming it
into the container via a FIFO pipe.
Container: fake rec/arecord shims read PCM from a FIFO instead of
a real mic. Audio bridge exec writes PCM from Tauri into the FIFO.
Frontend: getUserMedia() + AudioWorklet captures 16kHz mono PCM
and streams it to the container via invoke("send_audio_data").
UI: "Mic Off/On" toggle button in the terminal view.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Intercept clipboard paste events containing images in the terminal,
upload them into the Docker container via bollard's tar upload API,
and inject the resulting file path into terminal stdin so Claude Code
can reference the image.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move git_token and Bedrock credentials to OS keychain instead of
storing in plaintext projects.json via skip_serializing + keyring
- Fix project status stuck in Starting on container creation failure
by resetting to Stopped on any error path
- Add granular store methods to reduce TOCTOU race window
- Add auth_mode, project path, and bedrock config change detection
to container_needs_recreation with label-based fingerprinting
- Fix mutex held across async Docker API call in exec resize by
cloning exec_id under lock then releasing before API call
- Add graceful shutdown via on_window_event to clean up exec sessions
- Extract compute_env_fingerprint and merge_claude_instructions helpers
to eliminate code duplication in container.rs
- Remove unused thiserror dependency
- Return error instead of falling back to CWD when data dir unavailable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
close_all_sessions() was called when stopping/removing/rebuilding a
project, which shut down exec sessions for every project. Track
container_id per session and use close_sessions_for_container() to
only close sessions belonging to the target project.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Entrypoint now runs as root to remap the container's claude user
UID/GID to match the host user, fixing bind mount permission errors
on WSL
- SSH keys are mounted read-only to a staging path (/tmp/.host-ssh)
and copied to ~/.ssh with correct permissions by the entrypoint
- Exec sessions explicitly run as the claude user
- Host UID/GID detected automatically and passed as env vars
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tauri v2 desktop app (React/TypeScript + Rust) for managing
containerized Claude Code environments. Includes Gitea Actions
workflow for building and pushing the sandbox container image,
and a BUILDING.md guide for manual app builds on Linux and Windows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>