1. Fix stale AWS/Bedrock auth carrying over on backend switch
Switching a project's backend (e.g. Bedrock → Anthropic) left the recreated container still authenticating against Bedrock, and SSO kept firing after switching away. Root causes + fixes:
Snapshot bakes env. Recreation builds the new container from a docker commit snapshot, and commit always bakes the previous container's ENV into the image (an empty commit Config does not strip it, and the commit API can't remove env). create_container now explicitly clears every managed auth key the active backend doesn't set (MANAGED_AUTH_KEYS), so create-time env overrides the stale baked-in values.
Persisted awsAuthRefresh. It was written to ~/.claude.json (persisted home volume) and never removed, so Claude Code kept invoking triple-c-sso-refresh after switching to a non-SSO backend. The entrypoint now deletes it when AWS_SSO_AUTH_REFRESH_CMD is unset (idempotent both ways).
Static creds refreshed per start. Static/session creds were baked into Config.Env at create time, so a stop/start kept stale creds. They're now written to ~/.aws/credentials (0600, secrets via exec env not argv) on every start, a stale ~/.aws/config from a prior profile/SSO session is removed, and they're dropped from the bedrock fingerprint so a key rotation refreshes in place instead of forcing a recreation.
2. "Backup" action — download a project's /workspace as .tar.gz
Manual button on each project card (next to Start/Reset when stopped, next to Files when running). Runs tar czf - inside the container (excludes + gzip happen in-container, so a large workspace isn't streamed in full), excluding regenerable node_modules/target/.git/objects, and streams to a host file via the native save dialog.
3. Backup also captures sanitized home config
So MCP servers, settings, and skills set up directly via Claude Code (stored in ~/.claude.json / ~/.claude, on the volumes a Reset wipes) survive a backup/restore. Secrets are stripped: ~/.claude.json is filtered through jq to drop primaryApiKey/oauthAccount/customApiKeyResponses (mcpServers/settings kept), and ~/.claude/.credentials.json (OAuth tokens) is omitted. Staged under home-claude/ in the archive.
4. Terminal file drag-and-drop
Drop files onto a terminal pane → copied into the container at /tmp/triple-c-drops/<name> and their paths typed into the prompt for Claude Code to read (mirrors image-paste). Uses Tauri's webview onDragDropEvent, guarded by the pane's active flag + a bounds hit-test so a drop only affects the terminal it landed on.
Docker commit/env-precedence, tar excludes + GNU-tar/jq behavior, sanitized home config, and credential-file precedence all verified against real containers.
Drag-drop is verified by build/types only (no display in CI) — worth a manual smoke test in tauri dev. If core:default doesn't grant the webview drag-drop event in this setup, it's a one-line capability add.
## Summary
Four related changes, all on top of latest `main`.
### 1. Fix stale AWS/Bedrock auth carrying over on backend switch
Switching a project's backend (e.g. Bedrock → Anthropic) left the recreated container still authenticating against Bedrock, and SSO kept firing after switching away. Root causes + fixes:
- **Snapshot bakes env.** Recreation builds the new container from a `docker commit` snapshot, and commit always bakes the previous container's ENV into the image (an empty commit Config does **not** strip it, and the commit API can't remove env). `create_container` now explicitly clears every managed auth key the active backend doesn't set (`MANAGED_AUTH_KEYS`), so create-time env overrides the stale baked-in values.
- **Persisted `awsAuthRefresh`.** It was written to `~/.claude.json` (persisted home volume) and never removed, so Claude Code kept invoking `triple-c-sso-refresh` after switching to a non-SSO backend. The entrypoint now deletes it when `AWS_SSO_AUTH_REFRESH_CMD` is unset (idempotent both ways).
- **Static creds refreshed per start.** Static/session creds were baked into `Config.Env` at create time, so a stop/start kept stale creds. They're now written to `~/.aws/credentials` (0600, secrets via exec env not argv) on every start, a stale `~/.aws/config` from a prior profile/SSO session is removed, and they're dropped from the bedrock fingerprint so a key rotation refreshes in place instead of forcing a recreation.
### 2. "Backup" action — download a project's /workspace as .tar.gz
Manual button on each project card (next to Start/Reset when stopped, next to Files when running). Runs `tar czf -` inside the container (excludes + gzip happen in-container, so a large workspace isn't streamed in full), excluding regenerable `node_modules`/`target`/`.git/objects`, and streams to a host file via the native save dialog.
### 3. Backup also captures sanitized home config
So MCP servers, settings, and skills set up directly via Claude Code (stored in `~/.claude.json` / `~/.claude`, on the volumes a Reset wipes) survive a backup/restore. Secrets are stripped: `~/.claude.json` is filtered through jq to drop `primaryApiKey`/`oauthAccount`/`customApiKeyResponses` (mcpServers/settings kept), and `~/.claude/.credentials.json` (OAuth tokens) is omitted. Staged under `home-claude/` in the archive.
### 4. Terminal file drag-and-drop
Drop files onto a terminal pane → copied into the container at `/tmp/triple-c-drops/<name>` and their paths typed into the prompt for Claude Code to read (mirrors image-paste). Uses Tauri's webview `onDragDropEvent`, guarded by the pane's `active` flag + a bounds hit-test so a drop only affects the terminal it landed on.
## Verification
- `cargo check` ✅, `tsc --noEmit` ✅, `vite build` ✅, vitest 33/33 ✅ (7 pre-existing unhandled-error logs, unchanged).
- Docker `commit`/env-precedence, `tar` excludes + GNU-tar/jq behavior, sanitized home config, and credential-file precedence all verified against real containers.
- Drag-drop is verified by build/types only (no display in CI) — worth a manual smoke test in `tauri dev`. If `core:default` doesn't grant the webview drag-drop event in this setup, it's a one-line capability add.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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>
Adds a manual backup button on each project card (next to Start/Reset
when stopped, and next to Files when running) that saves a gzipped
tarball of the container's /workspace to a host path via the native save
dialog.
Backend: download_container_backup runs `tar czf -` inside the container
(so excludes + compression happen there rather than streaming a 16 GB
workspace) and pipes stdout straight to the chosen file. Regenerable
build artifacts (node_modules, target, .git/objects) are excluded so the
archive stays restore-sized. Returns bytes written; stderr is captured
for error reporting and a zero-byte result is treated as failure.
Works whether the container is running or stopped (only requires that it
exists). Verified on the Ubuntu/GNU-tar container base.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends download_container_backup to also capture the container's home
config so MCP servers, settings, and skills set up directly via Claude
Code (stored in ~/.claude.json / ~/.claude, on the home/config volumes
that a Reset wipes) survive a backup/restore cycle.
Secrets are stripped per the "exclude secrets" choice: ~/.claude.json is
filtered through jq to drop primaryApiKey/oauthAccount/customApiKeyResponses
(mcpServers and settings are kept), and ~/.claude/.credentials.json (the
OAuth tokens) is omitted. Staged config is archived under home-claude/ in
the tarball. Verified on the Ubuntu/jq container base.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop files onto a terminal pane and they're copied into the container and
their in-container paths typed into the prompt, so Claude Code can read
them for reference — mirroring the existing image-paste flow.
Backend: upload_host_file_to_terminal reads the dropped host file and
writes it under /tmp/triple-c-drops/<name> in the session's container,
returning that path. Rejects directories and unreadable paths.
Frontend: TerminalView subscribes to Tauri's webview onDragDropEvent
(OS file drops are intercepted at the webview level, so HTML5 ondrop
wouldn't expose paths). The window-wide event is guarded by the pane's
`active` flag plus a bounds hit-test so a drop only affects the terminal
it landed on; multiple files are uploaded and their paths inserted
space-separated (quoted when they contain spaces).
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>
- L4: sync_bedrock_credentials (renamed from write_bedrock_static_
credentials) now also clears a stale ~/.aws/credentials when the
project no longer uses static-credential Bedrock, so static keys don't
linger unused in the persistent home volume after switching backends.
Skipped when /tmp/.host-aws is mounted (host-managed ~/.aws). HOME is
also set explicitly on the exec env for robustness.
- M1: the Backup button now has a tooltip and the success toast notes
that the archive includes MCP/config which may contain MCP-embedded
API keys (OAuth tokens are excluded) — keep it private.
- L2: backup now uses async file IO (tokio::fs::File + AsyncWriteExt,
tokio::fs::remove_file) instead of blocking std::fs between awaits;
dropped-file reads use tokio::fs::metadata/read.
- L5: upload_host_file_to_terminal explicitly `mkdir -p`s
/tmp/triple-c-drops instead of relying on Docker's tar extractor to
create the parent dir.
Verified L4 cleanup guard, L5 mkdir, async IO, and exit-code paths
against real containers. 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>
- 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>
- 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>
jknapp
merged commit dc253e8da0 into main2026-06-30 22:20:00 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Four related changes, all on top of latest
main.1. Fix stale AWS/Bedrock auth carrying over on backend switch
Switching a project's backend (e.g. Bedrock → Anthropic) left the recreated container still authenticating against Bedrock, and SSO kept firing after switching away. Root causes + fixes:
docker commitsnapshot, and commit always bakes the previous container's ENV into the image (an empty commit Config does not strip it, and the commit API can't remove env).create_containernow explicitly clears every managed auth key the active backend doesn't set (MANAGED_AUTH_KEYS), so create-time env overrides the stale baked-in values.awsAuthRefresh. It was written to~/.claude.json(persisted home volume) and never removed, so Claude Code kept invokingtriple-c-sso-refreshafter switching to a non-SSO backend. The entrypoint now deletes it whenAWS_SSO_AUTH_REFRESH_CMDis unset (idempotent both ways).Config.Envat create time, so a stop/start kept stale creds. They're now written to~/.aws/credentials(0600, secrets via exec env not argv) on every start, a stale~/.aws/configfrom a prior profile/SSO session is removed, and they're dropped from the bedrock fingerprint so a key rotation refreshes in place instead of forcing a recreation.2. "Backup" action — download a project's /workspace as .tar.gz
Manual button on each project card (next to Start/Reset when stopped, next to Files when running). Runs
tar czf -inside the container (excludes + gzip happen in-container, so a large workspace isn't streamed in full), excluding regenerablenode_modules/target/.git/objects, and streams to a host file via the native save dialog.3. Backup also captures sanitized home config
So MCP servers, settings, and skills set up directly via Claude Code (stored in
~/.claude.json/~/.claude, on the volumes a Reset wipes) survive a backup/restore. Secrets are stripped:~/.claude.jsonis filtered through jq to dropprimaryApiKey/oauthAccount/customApiKeyResponses(mcpServers/settings kept), and~/.claude/.credentials.json(OAuth tokens) is omitted. Staged underhome-claude/in the archive.4. Terminal file drag-and-drop
Drop files onto a terminal pane → copied into the container at
/tmp/triple-c-drops/<name>and their paths typed into the prompt for Claude Code to read (mirrors image-paste). Uses Tauri's webviewonDragDropEvent, guarded by the pane'sactiveflag + a bounds hit-test so a drop only affects the terminal it landed on.Verification
cargo check✅,tsc --noEmit✅,vite build✅, vitest 33/33 ✅ (7 pre-existing unhandled-error logs, unchanged).commit/env-precedence,tarexcludes + GNU-tar/jq behavior, sanitized home config, and credential-file precedence all verified against real containers.tauri dev. Ifcore:defaultdoesn't grant the webview drag-drop event in this setup, it's a one-line capability add.🤖 Generated with Claude Code