v0.3.76-mac
220
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
584fcdd837 |
Merge pull request #15: create the WOW64 junctions in the workflow instead of by hand
Build App / compute-version (push) Successful in 16s
Build App / build-macos (push) Successful in 2m38s
Build App / build-windows (push) Successful in 5m21s
Build App / build-linux (push) Successful in 5m48s
Build App / create-tag (push) Successful in 6s
Build App / sync-to-github (push) Successful in 14s
Moves the last hand-made piece of the Windows fix into version control. Verified by deleting the manual junctions from the build VM first, so CI had to recreate them from scratch.v0.3.76-mac v0.3.76-win v0.3.76 |
||
|
|
2c014fd752 |
CI: create the WOW64 junctions in the workflow instead of by hand
Build App / compute-version (pull_request) Successful in 6s
Build App / build-macos (pull_request) Successful in 2m29s
Build App / build-windows (pull_request) Successful in 5m52s
Build App / build-linux (pull_request) Successful in 6m12s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
The Windows fix was the only part of it living outside git — two junctions created by hand on the build VM. Rebuild that VM, add a second Windows runner, or reset the SYSTEM profile and Windows builds break again with an error that points nowhere near the cause. Tauri downloads candle.exe, light.exe and makensis.exe, and all three are 32-bit. A runner running as SYSTEM has %LOCALAPPDATA% under C:\Windows\System32\config\systemprofile, and WOW64 redirection serves 32-bit processes reading System32 from SysWOW64, where those directories do not exist. The bundlers cannot see their own folder: candle exits 0x80131700, makensis reports "Unable to start child process, error 0x2", and Tauri surfaces neither — only "failed to run candle.exe". The job now junctions the SysWOW64 view onto the System32 originals when it detects a profile inside System32, and skips entirely otherwise, so a runner running as a normal user is unaffected. Idempotent, and written with goto rather than nested blocks to avoid the delayed-expansion trap that already bit the MSVC step. Verified rather than assumed: the hand-made junctions were deleted from the build VM before this was pushed, so this run has to recreate them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
43e9959e40 |
Merge pull request 'Add llama.cpp + OpenAI backends, URL relay, browser view, and base-image migration' (#14) from feature/model-backends-and-browser into main
Build App / compute-version (push) Successful in 12s
Build Container / build-container (push) Successful in 50s
Build App / build-macos (push) Successful in 2m34s
Build App / build-windows (push) Successful in 5m25s
Build App / build-linux (push) Successful in 6m40s
Build App / create-tag (push) Successful in 6s
Build App / sync-to-github (push) Successful in 14s
|
||
|
|
e05156fd0e |
Merge branch 'main' into feature/model-backends-and-browser
Build App / compute-version (pull_request) Successful in 3s
Build Container / build-container (pull_request) Successful in 33s
Build App / build-macos (pull_request) Successful in 2m30s
Build App / build-windows (pull_request) Successful in 5m12s
Build App / build-linux (pull_request) Successful in 7m40s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
|
||
|
|
aca6c49e3c |
Merge pull request #13: Drop MCP management; add permission modes, Project Home, Auth Bridge and shared auth
Build App / compute-version (push) Successful in 11s
Build Container / build-container (push) Successful in 1m45s
Build App / build-macos (push) Successful in 2m29s
Build App / build-windows (push) Successful in 5m6s
Build App / build-linux (push) Successful in 6m31s
Build App / create-tag (push) Successful in 3s
Build App / sync-to-github (push) Successful in 10s
Claude Code absorbed MCP natively, so Triple-C stops managing it. Also adds permission modes, Project Home, Tier-1 accessibility fixes, the Auth Bridge, shared Claude authentication and the scheduler UI. 8 commits plus review fixes. 87 frontend tests, 34 Rust tests. |
||
|
|
0aa8315514 |
CI: restore the MSI now that the 32-bit bundlers can resolve their paths
Build App / compute-version (pull_request) Successful in 3s
Build Container / build-container (pull_request) Successful in 32s
Build App / build-macos (pull_request) Successful in 2m26s
Build App / build-windows (pull_request) Successful in 4m51s
Build App / build-linux (pull_request) Successful in 6m11s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
Dropping the MSI did not help: makensis.exe is 32-bit like candle.exe
and failed the same way ("Unable to start child process, error 0x2").
The cause was WOW64 redirection sending 32-bit processes reading
C:\Windows\System32 to SysWOW64, where the toolset directory does not
exist.
The build VM now carries junctions from the SysWOW64 view of
systemprofile\AppData\Local\tauri and systemprofile\.cache to the
System32 originals. Verified on the runner: candle.exe reports WiX
3.14.1.8722 and makensis reports v3.11, both exiting 0 from the path
that previously failed.
Both targets build again, so the .msi comes back. Artifact collection
fails if either installer is missing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
29fd7de909 |
CI: restore the MSI now that the 32-bit bundlers can resolve their paths
Build App / compute-version (pull_request) Successful in 6s
Build Container / build-container (pull_request) Successful in 1m40s
Build App / build-macos (pull_request) Successful in 2m32s
Build App / build-windows (pull_request) Successful in 5m13s
Build App / build-linux (pull_request) Successful in 6m29s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
Dropping the MSI did not help, because the problem was never WiX. makensis.exe is 32-bit exactly like candle.exe and light.exe, lives in the same SYSTEM-profile cache, and failed the same way — "Unable to start child process, error 0x2" instead of 0x80131700. The cause is WOW64 redirection: a 32-bit process reading C:\Windows\System32 is served C:\Windows\SysWOW64, where the toolset directory does not exist, so the bundlers cannot see their own folder. The build VM now carries two junctions from the SysWOW64 view of systemprofile\AppData\Local\tauri and systemprofile\.cache to the System32 originals. Verified afterwards on the runner: candle.exe reports "WiX Toolset Compiler version 3.14.1.8722" and exits 0, and makensis reports v3.11 and exits 0 — both from the same path that failed before. So both targets build again and the .msi asset comes back. Artifact collection fails if either installer is missing rather than tolerating an empty directory. This is a host-side patch for a runner running as SYSTEM. A runner running as a normal user has a LOCALAPPDATA outside System32 and needs none of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fdc161fd9c |
CI: build NSIS only on Windows, dropping the MSI target
Build App / compute-version (pull_request) Successful in 17s
Build Container / build-container (pull_request) Successful in 1m22s
Build App / build-macos (pull_request) Successful in 2m22s
Build App / build-windows (pull_request) Failing after 4m41s
Build App / build-linux (pull_request) Successful in 5m51s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
WiX's candle.exe/light.exe are 32-bit. On a SYSTEM-run runner Tauri caches WiX under C:\Windows\system32\config\systemprofile\..., and WOW64 redirection sends 32-bit processes to SysWOW64 where that directory does not exist, so candle exits 0x80131700. Tauri aborts the whole bundle on one target's failure, so the MSI was suppressing the NSIS installer too and Windows produced no artifact at all. NSIS is what the project already relies on for Windows upgrades. Drops the .NET 3.5 gate, which existed only for WiX; keeps the MSVC step, which is what makes the app link. Artifact collection now fails when no installer is produced rather than tolerating an empty directory. To restore the MSI, run the runner as a normal user and set --bundles msi,nsis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
98a6c8fd56 |
CI: build NSIS only on Windows, dropping the MSI target
The MSI target needs WiX, whose candle.exe and light.exe are 32-bit. On a runner running as SYSTEM, Tauri caches the WiX toolset under %LOCALAPPDATA% = C:\Windows\system32\config\systemprofile\..., and WOW64 redirection points 32-bit processes at SysWOW64, where that directory does not exist. candle.exe cannot see its own folder, the CLR fails to start, and it exits 0x80131700 — surfaced only as "failed to run candle.exe". Proven by running the identical toolset, as the same SYSTEM identity, from C:\wixtest (exit 0) versus the systemprofile path (0x80131700). Because Tauri aborts the entire bundle when one target fails, the MSI was also suppressing the NSIS installer — so Windows produced no artifact at all. NSIS is what the project already relies on for Windows upgrades, so dropping MSI costs the .msi asset and nothing else. Removes the .NET 3.5 gate, which only existed for WiX. The MSVC step stays: that is what makes the app link, and it works. Artifact collection now fails when no NSIS installer is present instead of tolerating an empty directory with 2>nul, so a silent packaging regression cannot pass as a green build again. To restore the MSI later, run the runner as a normal user — whose LOCALAPPDATA sits outside System32 — and set --bundles msi,nsis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
763af91042 |
Revert: LOCALAPPDATA override does not move Tauri's WiX cache
Build App / compute-version (pull_request) Successful in 3s
Build Container / build-container (pull_request) Successful in 32s
Build App / build-macos (pull_request) Successful in 2m23s
Build App / build-windows (pull_request) Failing after 4m35s
Build App / build-linux (pull_request) Successful in 5m6s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
Rust's `dirs` crate resolves LOCALAPPDATA on Windows through SHGetKnownFolderPath, which reads the process token rather than the environment, so the override changed nothing and 32-bit candle.exe still hit WOW64 redirection under the SYSTEM profile. Removing it rather than leaving a plausible-looking non-fix in the workflow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c71e54a35f |
Revert: LOCALAPPDATA override does not move Tauri's WiX cache
It looked right and did nothing. Rust's `dirs` crate resolves LOCALAPPDATA on Windows through SHGetKnownFolderPath, which reads the process token rather than the environment, so Tauri still cached the WiX toolset under the SYSTEM profile and 32-bit candle.exe still hit WOW64 redirection. Removing it rather than leaving a plausible-looking non-fix in the workflow. The diagnosis in the previous commit stands; only the remedy was wrong. Running the runner as a normal user, whose token resolves LOCALAPPDATA outside System32, is the actual fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
03384409e7 |
CI: keep the WiX toolset out of System32 so 32-bit candle.exe can run
Build App / compute-version (pull_request) Successful in 13s
Build Container / build-container (pull_request) Successful in 44s
Build App / build-macos (pull_request) Successful in 2m24s
Build App / build-windows (pull_request) Failing after 4m39s
Build App / build-linux (pull_request) Successful in 6m44s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
WiX's candle.exe/light.exe are 32-bit. A SYSTEM-run runner has %LOCALAPPDATA% under C:\Windows\system32\config\systemprofile, where Tauri caches the WiX toolset — and WOW64 redirection sends 32-bit processes reading System32 to SysWOW64, which has no such directory. The CLR then fails to start with 0x80131700 and Tauri reports only "failed to run candle.exe". Verified: the same binary and identity exits 0 from C:\wixtest and 0x80131700 from the systemprofile path. Pointing LOCALAPPDATA outside System32 avoids redirection, needs no stored credential, and is a no-op for runners already running as a normal user. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b41077e799 |
CI: keep the WiX toolset out of System32 so 32-bit candle.exe can run
build-windows compiled and linked fine but died at bundling with only "failed to run candle.exe". The real cause was neither .NET nor the runner identity, both of which I chased first and was wrong about. candle.exe and light.exe are 32-bit. A runner running as SYSTEM has %LOCALAPPDATA% = C:\Windows\system32\config\systemprofile\AppData\Local, which is where Tauri caches the WiX toolset. WOW64 redirection sends any 32-bit process reading C:\Windows\System32 to C:\Windows\SysWOW64 — and the WixTools directory exists only in the 64-bit view. So candle.exe could not see its own directory, the CLR failed to start, and the process exited 0x80131700, surfaced in the Application event log as ".NET Runtime version 4.0.30319.0 - This application could not be started." Proven rather than assumed: copying the identical toolset to C:\wixtest and running it as the same SYSTEM identity exits 0, while the systemprofile path exits 0x80131700. Test-Path confirms the WOW64 view of that directory does not exist. Pointing LOCALAPPDATA at a path outside System32 avoids redirection. This fixes it for any runner running as a service or as SYSTEM, without needing a stored user credential, and is a no-op where the runner already runs as a normal user. For the record, two earlier theories were wrong. .NET 3.5 was missing and is now installed from the ISO payload, but candle targets .NET 4.x (its config uses loadFromRemoteSources, a 4.0-only element) so that was never the blocker. Adding explicit supportedRuntime entries changed nothing. Both are documented here so the next person does not repeat them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c6bb7fdf1d |
CI: fix the MSVC exit-code check and verify .NET 3.5 before bundling
Build App / compute-version (pull_request) Successful in 6s
Build Container / build-container (pull_request) Successful in 1m48s
Build App / build-windows (pull_request) Failing after 5m14s
Build App / build-linux (pull_request) Successful in 6m2s
Build App / build-macos (pull_request) Successful in 2m24s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
%VSEXIT% and %ERRORLEVEL% inside a parenthesised cmd block are substituted at parse time, not run time, so the installer's real exit code was never read. Uses delayed expansion now. Also checks for the .NET 3.5 runtime before building: WiX candle.exe needs it, and Tauri aborts the whole bundle when the MSI target fails, which silently suppresses the NSIS installer too. Fails early with the exact dism command rather than at bundle time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
704d3b8f79 |
CI: fix the MSVC exit-code check and verify .NET 3.5 before bundling
Two follow-ups to the provisioning step. The exit-code check never worked. %VSEXIT% and %ERRORLEVEL% inside a parenthesised cmd block are substituted when the block is PARSED, not when it runs, so the installer's real result was never read — the log printed "installer failed with " with an empty code, then continued anyway. It happened to be harmless because the install had in fact succeeded, but a genuine failure would have sailed past. Now uses delayed expansion. Added a .NET 3.5 check. WiX 3.x candle.exe is a .NET 2.0/3.5 application, and Tauri aborts the entire bundle when the MSI target fails — so a missing runtime silently costs the NSIS installer too, not just the MSI. Windows 11 ships NetFx3 as DisabledWithPayloadRemoved and Windows Update could not supply the payload on our runner even across a reboot; it needed /Source from a mounted ISO. Rather than guess, the job now fails early with the exact dism command. Verified on the runner: MSVC Build Tools 2022 installed, the Rust build completed in 3m59s and produced triple-c.exe, and NetFx3 is now Enabled with v2.0.50727 present. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ca0f944712 |
CI: install MSVC build tools on Windows runners that lack them
Build App / compute-version (pull_request) Successful in 4s
Build Container / build-container (pull_request) Successful in 30s
Build App / build-linux (pull_request) Successful in 5m24s
Build App / build-windows (pull_request) Failing after 15m29s
Build App / build-macos (pull_request) Successful in 2m24s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
build-windows failed on this PR with "linker `link.exe` not found", while build-linux and build-container passed — the code was fine, the runner environment was not. The job installs Rust and Node conditionally but assumed the MSVC C++ toolchain was hand-provisioned. A runner without it registers normally, advertises windows-latest, accepts the job, downloads the entire crate graph and only then fails at link time. That also means a bare runner coming online turns a job that would have queued for a capable machine into a failed build. Installs the VC++ workload when vswhere cannot find it, matching the existing conditional Rust and Node steps. rustc locates MSVC through vswhere and the registry rather than PATH, so no dev-shell activation is needed. Installer exit 3010 (success, reboot pending) is treated as success. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2de00b3c55 |
Fix review findings: secrets in snapshots, URL spoofing, migration data loss
Adversarial review of the branch produced findings across four areas. This addresses them, plus the Windows CI environment. Secrets. commit_container_snapshot baked the container's full env into the per-project snapshot image, so the shared OAuth token — and the AWS keys, git token and gateway master key — outlived revocation and were readable via docker inspect. Verified against Engine 29.6 that a commit body's config merges over the container's: keys cannot be dropped but can be overwritten, so all of them now commit as KEY=. clear_claude_token additionally rewrites images from earlier builds and reports honestly when a tag could not be rewritten. The recommendation to move the token out of env entirely was not taken, with reasoning: apiKeyHelper is a different auth method that outranks CLAUDE_CODE_OAUTH_TOKEN rather than a transport for it, and no file-based delivery exists. The durable exposure — the image — is what is closed here. Separately noted, not fixed: entrypoint.sh captures the token into the scheduler's .env inside the persisted volume. URL spoofing. Three call sites reached openUrl with container-controlled strings, one of which the review missed (the WebLinksAddon handler). The sign-in URL was scraped from container output with a longest-match tie-break and no userinfo check, so claude.ai@evil.tld rendered as "claude.ai…" in a truncating element. There is now one sanitizer in front of every sink — scheme allowlist, no userinfo, C0/C1 and quote rejection, host allowlist for the sign-in case, first-match — and the origin renders un-truncated. The toast is keyed so a changed URL remounts, closing a bait-and-switch where the user read one URL and clicked another. Migration. The rollback pin was best-effort: a tag failure was logged and the migration continued past remove_container, after which the final commit overwrote the only copy of the old system layer. It now aborts before anything destructive and reads the tag back. /var was destroyed while the ordinary recreate path preserves it — making the "safe" alternative to Reset more destructive than Reset's alternative; data-bearing subtrees are now detected and disclosed in the pre-flight rather than copied, since tarring a live database onto a different base's packages is a corruption risk. resume_migration now verifies the migration-state label instead of reporting success for a container that never swapped. dismiss actually resolves the record rather than leaving the feature permanently refusing to migrate. Start and Reset are guarded while a migration is live. Lifecycle. The gateway no longer publishes on 0.0.0.0 — bind address and advertised URL are derived together so they cannot drift. Disabling it now stops it. App exit runs teardown concurrently under a budget with a visible shutting-down state instead of blocking for minutes. Auto-starts retry when Docker is not up yet, and the polling-recovery path now reconciles, so interrupted migrations are still recovered. Auth-bridge forwards are capped, closing a container-driven fd exhaustion. Windows CI. build-windows failed on this branch with "linker link.exe not found". The runner had no MSVC build tools and the workflow assumed a hand-provisioned machine, so a bare runner registers, accepts jobs and fails at link time after downloading the whole crate graph. The job now installs the VC++ workload when vswhere cannot find it, matching how it already conditionally installs Rust and Node. 192 Rust tests, 274 frontend tests, both builds clean, zero warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
eb1324cb16 |
Fix pre-auth bypass in the browser-view proxy
read_head computed the head terminator index and discarded it, returning
the whole receive buffer. authorize() then split that buffer on CRLF and
treated every colon-bearing line as a header, last occurrence winning —
so any bytes a client sent after the head were promoted to headers.
A cross-site fetch with a text/plain body is CORS-safelisted and not
preflighted, so a body of
a=x\r\nSec-Fetch-Site: same-origin\r\n
overwrote the real cross-site value and the gate returned Allow. The
same trick overrode Host, defeating the anti-rebinding check too. The
result was unauthenticated mouse, keyboard and CDP control of a browser
running inside a container that has passwordless sudo — reachable from
any page the user happened to visit, with the port range being a fixed
8-wide window that is trivially scanned.
read_head now returns the terminator index and the caller authorizes
against that slice only, while still replaying the full buffer into the
tunnel so pipelined bodies are not lost. Duplicate Host, Origin and
Sec-Fetch-Site headers are now refused outright rather than resolved
last-wins, since that resolution is what turns any smuggling primitive
into a full bypass and no legitimate client sends two.
Three regression tests, including a guard assertion that the untruncated
buffer really was accepted before, so the test cannot quietly stop
testing the bypass.
Found by adversarial review, verified by extracting the real authorize()
and running the attack against it.
148 Rust tests, frontend build clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
d42b741337 |
Migrate a project onto a new base image without losing its volumes
Projects were pinned to the image they were first created from. Both create paths preferred triple-c-snapshot-<id>:latest whenever it existed, and container_needs_recreation compared the container's live image against the triple-c.image label — which create_container wrote from the same image it created from. A tautology that could never fire. The only escape was Reset, which calls remove_project_volumes and destroys the login, skills and transcripts. Measured consequences on this host: real projects are missing socat (so the auth bridge cannot tunnel) and bubblewrap (so sandbox mode does not work), plus Mission Control and triple-c-sso-refresh, and sit 61 packages behind the base including ca-certificates, openssl and curl. Detection. create_container now writes triple-c.base-image-id (the image ID, not RepoDigests, which local-built and custom images do not have) and triple-c.create-image. container_needs_recreation takes the expected create-image and compares against the latter, so the check means something. base-image-id is deliberately NOT compared: a base bump would otherwise silently recreate from the snapshot, consuming the "you should migrate" signal without migrating. Staleness is surfaced, never acted on automatically. Migration keeps the volumes. /home/claude and ~/.claude are volumes and the image's copy is seed-only — permanently masked after first mount — so the login, ~/.claude.json, skills, transcripts, scheduler tasks, SSH keys, cargo, uv, ruff and Claude Code itself re-attach untouched. Only root-level state is rebuilt: apt packages are replayed against the new base rather than copied, so no stale libc is dragged forward, and /usr/local, /opt and the non-bind-mounted parts of /workspace are copied verbatim with tar --skip-old-files so they can never clobber a newer base binary. docker diff is not used: on a snapshot-derived container it reports only changes since the last commit. Raw image-vs-image diffing is filtered through dpkg ownership because it otherwise lies — 8,677 raw path differences on a real project reduced to 2 genuinely user-authored files, both loose /workspace-root files. Crash safety. snapshot:latest keeps pointing at the old image until the final commit, so any crash before it self-heals on next start. Later crashes are caught by reconcile_project_statuses. The rollback pin is a docker tag: 0.057s and 0 bytes. Rollback restores the system layer only — volumes are never touched — and the UI says so rather than implying a time machine. Fixes an infinite recreation loop shipped with the MCP removal. docker commit propagates labels to the image, so a container created from a snapshot inherited its non-empty triple-c.mcp-fingerprint and the one-shot shim recreated it again on every start, forever. Lineage labels are now always written explicitly. Documents the second, separate bug this uncovered: Dockerfile changes under /home/claude never reach an existing project, migration or not, because the volume masks them. Anything that must stay upgradable belongs in /usr/local/bin or /opt, or must be seeded by entrypoint.sh. 145 Rust tests, 227 frontend tests, both builds clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
cc5f691677 |
Add llama.cpp backend, model gateway, URL relay and browser view
Four features, plus a latent bug fix.
llama.cpp backend. Claude Code only ever speaks the Anthropic Messages
API — confirmed empirically by pointing it at a logging server, which
received POST /v1/messages?beta=true. llama-server implements that
natively (verified in its README, alongside --port default 8080), so
this is a plain base-URL backend with no translation shim, the same
shape as Ollama. Its --api-key defaults to none, so the auth token is a
placeholder Claude Code requires and llama-server ignores.
Model alias fix. ANTHROPIC_DEFAULT_HAIKU_MODEL is documented as "also
used for background functionality", and Triple-C set none of the alias
vars. So on every custom-endpoint backend, Claude Code resolved `haiku`
to an Anthropic model id and sent it to a local server that does not
have it — background features failed silently. All four
ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU,FABLE}_MODEL vars are now pinned to
the backend's configured model, with an optional Haiku override, and
blanked for Anthropic and Bedrock so those keep Claude Code's defaults.
The deprecated ANTHROPIC_SMALL_FAST_MODEL is never emitted. Existing
Ollama and OpenAI-Compatible containers are recreated once so the new
env reaches them; the snapshot is preserved.
Model gateway. Optional LiteLLM sibling container, off by default,
mirroring stt.rs — this is what makes real OpenAI usable, since
api.openai.com has no /v1/messages. Pinned to v1.96.0 by tag and digest:
the 1.82.7/1.82.8 malware was PyPI-only and never affected the official
images, which is precisely why this builds FROM the image rather than
pip-installing, but 1.84.0 is still the floor for proxy CVEs (API-key
SQLi, Host-header auth bypass, MCP auth bypass). Binds 0.0.0.0 because
project containers consume it, and therefore always sets a master_key —
LiteLLM without one accepts any key. The provider key lives in the OS
keychain and is uploaded into a volume, never an image layer or label.
URL relay. A container-side xdg-open/BROWSER shim opens URLs in the
host's browser. Uses an OSC sequence to /dev/tty rather than a printed
sentinel, because the shim usually runs as a grandchild of a process
capturing its children's output. Degrades to printing the URL when no
terminal is attached, so scheduled tasks do not hang. Only http/https,
with control characters rejected before new URL() — which strips
newlines, so java\nscript: would otherwise parse as javascript:. Nothing
auto-opens; the user confirms. The web terminal shows a tap-to-open
banner instead, since that browser may be a phone across a tunnel.
Browser view. A Project Home tab that watches and takes over the browser
Claude drives with Playwright, using Playwright's own dashboard. Zero
image cost — Playwright stays user-installed. It does not reuse the auth
bridge's PortForward, which binds an unauthenticated port: correct for a
throwaway OAuth listener, wrong for mouse and keyboard control of a
browser in a passwordless-sudo container. Instead a token-gated loopback
proxy checks Host, then token or a forbidden-header origin signal,
before a byte reaches the container. Host ports are confined to
47820..=47827 so CSP frame-src can enumerate them rather than widening
to a wildcard, with a test asserting the two agree.
188 frontend tests, 107 Rust tests, both builds clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
7d00390e1f |
Add scheduled task creation, and stop a bad cron unscheduling everything
Build App / compute-version (pull_request) Successful in 4s
Build Container / build-container (pull_request) Successful in 9m35s
Build App / build-linux (pull_request) Successful in 5m35s
Build App / build-windows (pull_request) Failing after 2m26s
Build App / build-macos (pull_request) Successful in 2m49s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
Completes the Automation tab: it could list, toggle, run, log and remove tasks but not create them, so task creation still meant dropping to the CLI. Adds add_scheduled_task and update_scheduled_task, plus a task editor with cron presets and a plain-English reading of the expression. Every field is free user text, so all of it goes to the scheduler as a bare argv vector through bollard — no shell, no quoting. Validation is shape-only rather than metacharacter scrubbing: length caps, no control characters in single-line fields, no leading-dash name, absolute working_dir. Verified by round-tripping a prompt containing `; rm -rf /`, `$(id)`, backticks and newlines: it landed byte-for-byte in the task JSON with nothing executed. The scheduler CLI has no `edit`, so update is add-then-remove with the add first — a rejected edit leaves the original intact. The new id is surfaced in the editor rather than hidden. Root-cause fix, and the more serious half of this commit: triple-c-scheduler never validated --schedule, and rebuild_crontab regenerates the entire crontab and pipes it to `crontab`, which rejects the whole file if any line is malformed — with the error thrown away by `2>/dev/null || true`. A single bad schedule therefore silently unscheduled every other task in the container while reporting success. Reproduced directly. It matters because the global CLAUDE.md tells Claude to drive this CLI, so Claude could trigger it unprompted. `add` now validates the expression and exits non-zero, and rebuild_crontab reports a rejected crontab instead of swallowing it, keeping the offending file for inspection. Verified against the real CLI in this container: a bad schedule is refused without disturbing an existing task's crontab entry, and `0 9 * * 1-5`, `*/30 * * * *`, `0,30 8-17 * * *` and `0 0 1 1 *` are all still accepted. The Rust layer validates independently, agreeing with vixie cron on 23 probed expressions including `1/2` and `*/0` being invalid. 121 frontend tests, 44 Rust tests, both builds clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
cf3b021c72 |
Confirm before Reset, and rewrite the docs for the new UI
Reset is destructive in a way its name does not advertise: rebuild_project_container deletes both project volumes, so it wipes the claude login, anything installed in the container, and every saved session transcript. It was a single unconfirmed click in the overflow menu, while the comparably destructive Remove already confirmed. Adds ConfirmResetModal, which names each loss and says explicitly that the host-side mounted folders are untouched. Docs: the user guides still described the pre-Project-Home UI. Sixteen factually wrong statements corrected, including "expand the Config panel" (six sites), the actions table (Reset and Remove are in an overflow menu, Files is a tab), a progress modal that no longer exists, a double-click-to-rename gesture ProjectRow never had, the Full Permissions boolean, an incomplete reserved-env list, and the claim in TECHNICAL.md that OAuth tokens survive a Reset. Both layout diagrams and the project tree were rebuilt from the filesystem. New sections cover permission modes with the exact CLI mapping, Project Home, Sessions, capability tiles, Automation, shared authentication, the Auth Bridge and its security posture, and keyboard shortcuts. Known gap recorded rather than papered over: the Automation tab manages existing scheduled tasks but cannot create them — no add command is registered — so task creation remains `triple-c-scheduler add` in the terminal. 87 frontend tests, 34 Rust tests, both builds clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d95ba54a69 |
Add shared-auth-token UI and make cancelling actually cancel
UI for the shared Claude token: a Settings section showing token state with Authenticate and Revoke, an acquisition modal built on the shared Modal (sign-in link handed to the host browser via the opener plugin, plus the code input that answers `setup-token`'s stdin prompt — the flow cannot complete without it), and a per-project opt-out toggle shown only for the Anthropic backend. Cancellation: acquire_claude_token previously had only two exits, completion and a 15-minute timeout, and held the single-flight guard for the whole time. Closing the dialog therefore locked the user out of retrying for up to 15 minutes. Adds cancel_claude_token, backed by a oneshot claimed and released in lockstep with the input guard, selected on in the run loop so it wins the race and tears the exec down. The dialog's Cancel now calls it and closes either way. Also refreshes CLAUDE.md, which had drifted: it documented the deleted ProjectCard, and asserted that new IPC commands need permission grants in capabilities/default.json — they do not, that file covers plugin commands only. Adds the conventions that would otherwise bite: container_needs_recreation() is purely label-based and never diffs env, so container-affecting state needs its own label; and #[serde(default)] on a bool yields false regardless of intent. Corrects the claim that Reset preserves credentials. Reset calls remove_project_volumes, which deletes both the home and claude-config volumes, so it wipes ~/.claude, the OAuth token, installed skills and session transcripts. 84 frontend tests, 34 Rust tests, both builds clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
01a2f6aec8 |
Add Project Home, Auth Bridge, shared auth token, and Tier-1 polish
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>
|
||
|
|
f68d10d5c2 |
Add DESIGN-REVIEW.md and ROADMAP.md
DESIGN-REVIEW.md is Fable's review of the v0.3.0 UI: token gaps and three WCAG AA contrast failures, the modal/accessibility audit, and an IA proposal that promotes the project from a sidebar card to a tabbed main-area view. ROADMAP.md covers Claude Code feature coverage — the five settings.json keys currently surfaced, the gaps worth closing, the ones deliberately skipped, the authentication handoff design, and phase sequencing. Also published as an artifact for easier reading. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0ac4e5030c |
Add permission modes and container introspection backend
Permission modes: replaces the binary full_permissions flag with a PermissionMode enum (Plan/Default/AcceptEdits/Bypass). Flag mapping is defined once in PermissionMode::cli_args() and used by the terminal, the web terminal, and the scheduler: Plan -> --permission-mode plan Default -> (no flag) AcceptEdits -> --permission-mode acceptEdits Bypass -> --dangerously-skip-permissions Choices verified against `claude --permission-mode` on 2.1.226. full_permissions is retained and effective_permission_mode() falls back to it, so existing projects.json needs no migration. Bug fix: triple-c-task-runner ran `claude -p ... --dangerously-skip- permissions` unconditionally, ignoring the project's setting entirely. It now reads TRIPLE_C_PERMISSION_MODE, which is injected into the container, added to the reserved env blocklist, propagated through the entrypoint's cron env filter, and tracked by a new triple-c.permission-mode label so a change forces recreation. Introspection: new commands/inspect_commands.rs exposes read-only views into the container over docker exec — Claude sessions (parsed from ~/.claude/projects/<cwd>/<uuid>.jsonl), installed capabilities (skills, agents, commands, hooks, plugins, natively-configured MCP servers), and the triple-c-scheduler task list, logs and notifications. Task/session ids are validated against a strict allowlist and every parameterized call runs as a bare argv vector via bollard, so no shell is involved. Stopped containers return empty results rather than errors. No UI yet; that lands with the Project Home view. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d0bb631d4d |
Remove MCP backend, entrypoint injection, and docs; add migration shim
Completes the removal begun in the previous commit. Backend: deletes models/mcp_server.rs, storage/mcp_store.rs and commands/mcp_commands.rs, the McpStore on AppState, the four IPC handlers, Project::enabled_mcp_servers, build_mcp_servers_json(), compute_mcp_fingerprint(), the MCP_SERVERS_JSON env injection, the mcp-fingerprint label, and the whole MCP container lifecycle. create_container() and container_needs_recreation() lose their mcp_servers/network_name parameters. Container: entrypoint.sh no longer merges MCP_SERVERS_JSON into ~/.claude.json. MCP_SERVERS_JSON stays in the reserved env blocklist. Security: the Docker socket is no longer auto-mounted for stdio+Docker MCP servers — it now mounts only when allow_docker_access is set. Migration: old containers were created with network_mode=triple-c-net-<projectId> and refuse to start once that network is gone. docker/network.rs becomes docker/legacy_cleanup.rs with label-driven, best-effort removal of leftover MCP containers and the per-project network, called on both delete and recreate. container_needs_recreation() now forces a rebuild for any container carrying a non-empty triple-c.mcp-fingerprint label or attached to a triple-c-net-* network, moving it onto the default bridge. Both can be dropped a release later. Docs: drops the MCP sections from README/HOW-TO-USE/TECHNICAL and adds a short note pointing at Claude Code's native `claude mcp` / `/mcp` / .mcp.json instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
657c61939f |
Remove MCP tab and per-project MCP UI from frontend
Claude Code manages MCP natively now (`claude mcp add/list/remove`, `.mcp.json`, `/mcp`), so Triple-C's own MCP server library is redundant. Deletes components/mcp/, hooks/useMcpServers.ts, the MCP sidebar tab and rail icon, the per-project enable checkboxes on ProjectCard, the mcpServers slice of the Zustand store, the four IPC wrappers, and the McpServer/McpTransportType types. Rust backend is untouched in this commit; the commands simply become unreachable. Backend removal and the legacy container/network cleanup follow separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
401e28a658 |
Merge pull request 'Fix conflicting --global/--file flags in entrypoint git config' (#12) from fix/entrypoint-gitconfig-flags into main
Build Container / build-container (push) Successful in 2m47s
|
||
|
|
7c39e3cf11 |
Fix conflicting --global/--file flags in entrypoint git config
Build Container / build-container (pull_request) Successful in 10m15s
git rejects `--global` and `--file` together ("error: only one config
file at a time"), so the credential helper and user.name/user.email
were never written — /home/claude/.gitconfig was left nonexistent and
containers had no git identity or HTTPS token helper.
Drop `--global` and keep `--file /home/claude/.gitconfig`, which is the
intended target: the entrypoint runs as root at that point, so
`--global` would have resolved to /root/.gitconfig, and the existing
`chown claude:claude /home/claude/.gitconfig` already assumes the
--file path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ccdfc52dce | Merge pull request 'docs: document terminal layout & StatusBar control gotchas' (#11) from docs/terminal-layout-gotchas into main | ||
|
|
2e661979ea |
docs: document terminal layout & StatusBar control gotchas
Capture the non-obvious implementation gotchas from PR #7 (terminal-layout-statusbar) in TECHNICAL.md: wrapper-vs-host xterm padding, global StatusBar controls, recordingSessionIdRef transcript pinning, active-only Jump-to-Current state, and the Zustand object-merge rule for publishing action callbacks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
59d89bcd1b |
Merge pull request 'Rename backup archive root so extraction dir mode isn't clobbered' (#10) from fix/backup-root-dir-mode into main
Build App / compute-version (push) Successful in 4s
Build App / build-macos (push) Successful in 2m21s
Build App / build-windows (push) Successful in 4m35s
Build App / build-linux (push) Successful in 5m1s
Build App / create-tag (push) Successful in 4s
Build App / sync-to-github (push) Successful in 10s
|
||
|
|
26adccce5b |
Rename backup archive root so extraction dir mode isn't clobbered
Build App / compute-version (pull_request) Successful in 4s
Build App / build-macos (pull_request) Successful in 2m14s
Build App / build-windows (pull_request) Successful in 4m26s
Build App / build-linux (pull_request) Successful in 5m0s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
The transform used `s,^\./,workspace/,`, which rewrites the workspace *contents* (`./foo` -> `workspace/foo`) but leaves tar's root member as a bare `./`. That `./` entry carries the source root's mode/mtime, and on extraction tar stamps them onto the extraction directory itself. Match the leading `.` instead (`s,^\.,workspace,`) so the root member is renamed `./` -> `workspace`, giving the archive a proper `workspace/` directory entry and no bare `./`. The extraction directory is left untouched. Contents, hidden files, excludes, symlink targets and the `flags=rh` hardlink handling are unchanged. Verified in-container: archive top level is exactly `workspace/` + `home-claude/`, no `./` member, node_modules excluded, extraction into a 0755 dir leaves it 0755, workspace/.git preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
876ba8a8fc |
Merge pull request 'Nest workspace under workspace/ in project backup' (#9) from fix/backup-workspace-nesting into main
Build App / compute-version (push) Successful in 2s
Build App / build-macos (push) Successful in 2m22s
Build App / build-windows (push) Successful in 4m36s
Build App / build-linux (push) Successful in 5m5s
Build App / create-tag (push) Successful in 3s
Build App / sync-to-github (push) Successful in 12s
|
||
|
|
5cd528a4ef |
Use flags=rh so intra-workspace hardlinks survive the transform
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 4m24s
Build App / build-linux (pull_request) Successful in 5m3s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
Review caught that `flags=r` disables rewriting of both symlink AND
hardlink target names. Leaving symlink targets alone is intended, but a
hardlink's stored target is an archive-internal reference to another
member's name — when member names become `workspace/...` but the
hardlink target stays `./hard_link`, extraction fails hard:
tar: workspace/file.txt: Cannot hard link to './hard_link':
No such file or directory
`flags=rh` rewrites regular member names and hardlink target names
together (keeping the pair consistent) while still leaving symlink
targets untouched. Verified in-container: extract exit 0, symlink target
preserved, hardlink pair shares one inode, nesting under workspace/ intact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c3fc029b1d |
Nest workspace under workspace/ in project backup
Build App / compute-version (pull_request) Successful in 4s
Build App / build-macos (pull_request) Successful in 2m17s
Build App / build-linux (pull_request) Successful in 5m7s
Build App / build-windows (pull_request) Successful in 5m9s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
The backup archive placed the workspace at the archive root (`./...`) while the sanitized home config sat under `home-claude/`. On extraction the workspace files scattered loose into the extraction directory and only `home-claude/` showed up as a distinct folder, so the backup read as "config only, workspace missing" — and some archive viewers didn't surface the root-level entries at all. Add `--transform='flags=r;s,^\./,workspace/,'` so the workspace nests under `workspace/`, parallel to `home-claude/`. `flags=r` scopes the rewrite to member names only, leaving symlink targets (relative and absolute) intact. Excludes still match the pre-transform names. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dc253e8da0 |
Merge pull request 'Fix backend-switch AWS auth + add /workspace backup and terminal file drag-and-drop' (#8) from fix/backend-switch-aws-creds into main
Build App / compute-version (push) Successful in 3s
Build Container / build-container (push) Successful in 34s
Build App / build-macos (push) Successful in 2m23s
Build App / build-windows (push) Successful in 3m14s
Build App / build-linux (push) Successful in 6m22s
Build App / create-tag (push) Successful in 5s
Build App / sync-to-github (push) Successful in 10s
|
||
|
|
3e2e3f231b |
Third review pass: fix tar TOCTOU + transient backup status
Build App / compute-version (pull_request) Successful in 3s
Build Container / build-container (pull_request) Successful in 31s
Build App / build-macos (pull_request) Successful in 2m16s
Build App / build-windows (pull_request) Successful in 3m9s
Build App / build-linux (pull_request) Successful in 5m53s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
- 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> |
||
|
|
01a8f5c503 |
Apply remaining review findings (L-e, L-f)
Build App / compute-version (pull_request) Successful in 3s
Build Container / build-container (pull_request) Successful in 1m30s
Build App / build-macos (pull_request) Successful in 2m16s
Build App / build-windows (pull_request) Successful in 3m6s
Build App / build-linux (pull_request) Successful in 6m11s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
- 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> |
||
|
|
0945e21eb1 |
Address second review pass: fix start-time race + minor cleanups
Build App / compute-version (pull_request) Successful in 9s
Build Container / build-container (pull_request) Successful in 1m48s
Build App / build-macos (pull_request) Successful in 2m16s
Build App / build-windows (pull_request) Successful in 3m11s
Build App / build-linux (pull_request) Successful in 4m51s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
- 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> |
||
|
|
d65872dc94 |
Address remaining review items: L4/M1 + L2/L5 cleanup
Build App / compute-version (pull_request) Successful in 3s
Build Container / build-container (pull_request) Successful in 57s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 2m54s
Build App / build-linux (pull_request) Successful in 5m44s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
- 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> |
||
|
|
edf0698774 |
Address PR review: backup correctness/security + drop hardening
Build App / compute-version (pull_request) Successful in 8s
Build Container / build-container (pull_request) Successful in 51s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 2m52s
Build App / build-linux (pull_request) Successful in 6m5s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
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> |
||
|
|
84e0bdf7b4 |
Add file drag-and-drop onto the terminal
Build App / compute-version (pull_request) Successful in 6s
Build Container / build-container (pull_request) Successful in 1m25s
Build App / build-macos (pull_request) Successful in 2m31s
Build App / build-windows (pull_request) Successful in 4m14s
Build App / build-linux (pull_request) Successful in 4m54s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
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> |
||
|
|
10e689eaa6 |
Include sanitized home config in project backup
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> |
||
|
|
d07dcdfea9 |
Add "Backup" action to download a project's /workspace as .tar.gz
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> |
||
|
|
424ab04ca8 |
Fix stale AWS/Bedrock auth carrying over on backend switch
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> |
||
|
|
1716fb5c82 |
Merge pull request 'Fix xterm clipping; move mic + Jump to Current into status bar' (#7) from terminal-layout-statusbar into main
Build App / compute-version (push) Successful in 4s
Build App / build-macos (push) Successful in 2m20s
Build App / build-windows (push) Successful in 4m39s
Build App / build-linux (push) Successful in 4m58s
Build App / create-tag (push) Successful in 4s
Build App / sync-to-github (push) Successful in 10s
Reviewed-on: #7 |
||
|
|
da7b7b9bd5 |
Address review: pin STT transcript, clear stale scroll state
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 3m49s
Build App / build-linux (pull_request) Successful in 4m48s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
Follow-up to PR review on terminal-layout-statusbar: - [Major] Pin STT transcripts to the originating terminal. The single useSTT instance is bound to the live active session, which can change mid-recording. Capture the session id at recording start in a ref and inject the transcript there instead of the live sessionId, so text always lands in the terminal where recording began. - [Minor] Clear the status-bar scroll state when the active terminal unmounts, and null out termRef on dispose, so scrollActiveToBottom can't point at a disposed terminal. Tab switches don't unmount, so this only fires when the active session is actually closed. - [Nit] Fix the terminal padding comment to match the symmetric value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3d2d979197 |
Fix xterm clipping; move mic + Jump to Current into status bar
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m19s
Build App / build-linux (pull_request) Successful in 5m3s
Build App / build-windows (pull_request) Successful in 7m34s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
Terminal layout fixes for the xterm pane: - Stop the terminal grid from clipping its rightmost column / bottom row. The padding was on the element xterm mounts into, which the FitAddon measures; the grid overhang got clipped. Padding now lives on a wrapper and the xterm host fills it with no padding. - Move the STT mic from a floating bottom-left overlay into the status bar (far right). A single useSTT instance bound to the active session now lives in App; Ctrl+Shift+M routes through the store. - Move "Jump to Current" from a floating terminal overlay into the status bar. The active TerminalView surfaces its scroll state and scroll action via the store. - Tighten terminal padding (was 8/12/48/16) now that nothing floats over it, so the terminal claims as much area as possible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |