Two things the UI couldn't do: rearrange the tab strip, and watch the browser while working somewhere else. **Drag to reorder.** `moveTab`/`moveActiveTab` on the store, HTML5 drag on the strip with a marker showing where the drop lands, `Ctrl+Shift+←/→` for the same thing without a mouse. Reordering deliberately does not select what it moves, so a drag aimed at a background tab doesn't yank the main area away from a terminal mid-run. A tab being renamed is not draggable — a draggable ancestor swallows the mouse-drag that selects text in its input. **Pop the browser view out.** `browser_view/popout.rs` opens the view's existing token-bearing loopback URL as a second OS window, with a "Keep on top" toggle so it can float above the app. Window-only: the viewer, the proxy and the container are untouched, so popping out and back interrupts nothing. Three things it rests on: - No capability lists that window, so it has no IPC surface — right for a page served out of a container, and it must stay that way. - The app CSP is irrelevant to it: `frame-src` constrains what the app's document may *embed*, and this is a top-level document. The port range and the token gate are what actually protect it, unchanged. - The window is owned by the session, so the supervisor's teardown closes it. A window onto a viewer that no longer exists is worse than none. The pane drops its iframe while popped out — two viewers can both *drive* the browser, and two cursors on one page is not a feature. `lib.rs`'s `on_window_event` is now guarded on `label() == "main"`. It fires for every window and its body stops every container and exits, so without the guard closing a pop-out would quit the app. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
72 KiB
How to Use Triple-C
Triple-C (Claude-Code-Container) is a desktop application that runs Claude Code inside isolated Docker containers. Each project gets its own sandboxed environment with bind-mounted directories, so Claude only has access to the files you explicitly provide.
Table of Contents
- Prerequisites
- First Launch
- The Interface
- Project Home
- Project Management
- Permission Modes
- Project Configuration
- Shared Claude Authentication
- Opening URLs in Your Browser (URL Relay)
- Browser Logins Inside the Container (Auth Bridge)
- AWS Bedrock Configuration
- Ollama Configuration
- llama.cpp Configuration
- OpenAI Compatible Configuration
- Model Aliases and Background Calls
- Settings
- Web Terminal (Remote Access)
- Terminal Features
- Automation & Scheduled Tasks
- Keyboard Shortcuts
- What's Inside the Container
- Claude Code Tips
- Troubleshooting
Prerequisites
Docker
Triple-C requires a running Docker daemon. Install one of the following:
| Platform | Option | Link |
|---|---|---|
| Windows | Docker Desktop | https://docs.docker.com/desktop/install/windows-install/ |
| macOS | Docker Desktop | https://docs.docker.com/desktop/install/mac-install/ |
| Linux | Docker Engine | https://docs.docker.com/engine/install/ |
| Linux | Docker Desktop (alternative) | https://docs.docker.com/desktop/install/linux/ |
After installation, verify Docker is running:
docker info
Windows note: Docker Desktop must be running before launching Triple-C. The app communicates with Docker through the named pipe at
//./pipe/docker_engine.
Linux note: Your user must have permission to access the Docker socket (
/var/run/docker.sock). Either add your user to thedockergroup (sudo usermod -aG docker $USER, then log out and back in) or run Docker in rootless mode.
Claude Code Account
You need access to Claude Code through one of:
- Anthropic account — Sign up at https://claude.ai and use
claude login(OAuth) inside the terminal - AWS Bedrock — An AWS account with Bedrock access and Claude models enabled
- Ollama — A local or remote Ollama server (best-effort support)
- llama.cpp — A local or remote
llama-server(best-effort support) - OpenAI Compatible — A gateway that implements the Anthropic Messages API, such as LiteLLM (best-effort support). A server that only speaks OpenAI's
/v1/chat/completionswill not work — see OpenAI Compatible Configuration.
First Launch
1. Get the Container Image
When you first open Triple-C, go to the Settings tab in the sidebar. Under Docker, you'll see:
- Docker Status — Should show "Connected" (green). If it shows "Not Available", make sure Docker is running.
- Image Status — Will show "Not Found" on first launch.
Choose an Image Source:
| Source | Description | When to Use |
|---|---|---|
| Registry | Pulls the pre-built image from ghcr.io |
Fastest setup — recommended for most users |
| Local Build | Builds the image locally from the embedded Dockerfile | If you can't reach the registry, or want a custom build |
| Custom | Use any Docker image you specify | Advanced — bring your own sandbox image |
Click Pull Image (for Registry/Custom) or Build Image (for Local Build). A progress log will stream below the button. When complete, the status changes to "Ready" (green).
2. Create Your First Project
Switch to the Projects tab in the sidebar and click + Add.
- Project Name — Give it a meaningful name (e.g., "my-web-app").
- Folders — Click Browse to select a directory on your host machine. This directory will be mounted into the container at
/workspace/<folder-name>. You can add multiple folders with the + button at the bottom of the folder list. - Click Add Project.
3. Start the Container
Click the project in the sidebar. Its Project Home opens as a tab in the main area. Click Start in the Project Home header (or use the play control that appears when you hover the sidebar row).
Progress is reported inline — the sidebar row and the Project Home header show messages like
"Creating container…" and "Starting container…" while the status moves from Stopped (○) to
Starting (◐) to Running (●). Nothing blocks the rest of the app; if something fails you get a
toast with the full detail behind a Details disclosure.
4. Open a Terminal
Click Open Claude Terminal in the Project Home header, or press Ctrl+T. A new tab appears in the main tab strip and an xterm.js terminal loads.
Claude Code launches automatically. The project's permission mode decides how much it asks before acting — the default is to prompt before each tool call. See Permission Modes.
5. Authenticate
Anthropic — shared token (recommended):
Run claude setup-token once from Settings → Claude Authentication in the sidebar, and every
Anthropic-backend project uses that token without its own login. See
Shared Claude Authentication.
Anthropic — per-container OAuth:
- Type
claude loginor/loginin the terminal. - Claude prints an OAuth URL. Triple-C detects long URLs and shows a clickable toast at the top of the terminal — click Open to open it in your browser.
- Complete the login in your browser. The token is saved and persists across container stops, starts and recreations. A Reset deletes it — see below.
If the login hangs after the browser step, the callback could not reach the container. Enable the Auth Bridge for that project.
AWS Bedrock:
- Stop the container first (most settings can only be changed while stopped).
- Open the project's Config tab and, under Model, set Backend to Bedrock.
- Fill in your AWS credentials in the same section (see AWS Bedrock Configuration below).
- Start the container again.
Ollama:
- Stop the container first (most settings can only be changed while stopped).
- Open the project's Config tab and, under Model, set Backend to Ollama.
- Set the base URL of your Ollama server (defaults to
http://host.docker.internal:11434for a local instance). Set the Model to the model you want to use (required). - Make sure the model has been pulled in Ollama (e.g.,
ollama pull qwen3.5:27b) or used via Ollama cloud before starting. - Start the container again.
llama.cpp:
- Stop the container first (most settings can only be changed while stopped).
- Open the project's Config tab and, under Model, set Backend to llama.cpp.
- Set the base URL of your
llama-server(defaults tohttp://host.docker.internal:8080,llama-server's default port). Set the Model to the model it is serving. - Start the container again.
OpenAI Compatible:
- Stop the container first (most settings can only be changed while stopped).
- Open the project's Config tab and, under Model, set Backend to OpenAI Compatible.
- Set the base URL of your gateway (defaults to
http://host.docker.internal:4000, LiteLLM's default port). Optionally set an API key and model. - Start the container again.
The Interface
┌──────────────────────────────────────────────────────────────────────┐
│ [⌂ my-app] [▣ my-app ask] [▣ my-app (bash)] Docker ● Image ● ? │
├─────────────┬────────────────────────────────────────────────────────┤
│ Sidebar │ ┌──────────────────────────────────────────────────┐ │
│ │ │ my-app ● Running · up 2h 5m │ │
│ Projects │ │ [Open Claude Terminal] [Shell] [Files] │ │
│ Settings │ │ [Stop] [⋯] │ │
│ │ ├──────────────────────────────────────────────────┤ │
│ ● my-app │ │ Overview · Sessions · Automation · Config · Files│ │
│ ○ other │ ├──────────────────────────────────────────────────┤ │
│ │ │ │ │
│ │ │ (Project Home, or a terminal view) │ │
│ │ │ │ │
│ │ └──────────────────────────────────────────────────┘ │
├─────────────┴────────────────────────────────────────────────────────┤
│ 2 project(s) · 1 running · 2 terminal(s) Jump to Current ↓ │
└──────────────────────────────────────────────────────────────────────┘
-
Tab strip (top) — One strip holds every open tab, in the order you opened them. There are two kinds: Project Home tabs (
⌂glyph, project name, status glyph) and terminal tabs (▣glyph, plus a small badge showing the permission mode the terminal was launched with —plan,ask,editsorbypass). Bash shell tabs show a "(bash)" suffix. Right-click a terminal tab to rename it, jump to its project home, or close it; double-click to rename inline. There is no separate terminal tab bar and no "+" button — tabs appear when you open a project or a terminal.Drag a tab to reorder it. A line shows where it will land, and dropping it does not change which tab you are looking at — so you can rearrange the strip without pulling focus away from a terminal that is mid-run.
Ctrl+Shift+←andCtrl+Shift+→move the active tab the same way without the mouse. The order is per-session: it is not saved when you quit. -
Status indicators (top right) — Docker connection and container image availability. Each pairs a coloured dot with a word, so status is never conveyed by colour alone. The ? button opens the built-in help.
-
Sidebar — Toggle between the Projects list and the Settings panel. It collapses to a narrow icon rail with the chevron button, and remembers that choice.
-
Main area — Shows the active tab: a Project Home view or an xterm.js terminal. With no tabs open you get a welcome screen with Docker/image/project readiness checks.
-
StatusBar — Counts of total projects, running containers and open terminal sessions; the Jump to Current ↓ button when a terminal is scrolled up; and the microphone button when speech-to-text is enabled.
Project Home
Clicking a project in the sidebar opens Project Home in the main area. The sidebar row is only for selecting a project and for two quick controls that appear on hover — start/stop, and open a Claude terminal. Everything else about a project lives in Project Home.
The header shows the project name, its status, how long the container has been up, and the action buttons. Below that are six tabs:
| Tab | What it's for |
|---|---|
| Overview | The permission mode control, a summary of the backend and sandbox settings, capability tiles, recent sessions and scheduled tasks |
| Sessions | Past Claude Code conversations stored on this project's config volume, each with a Resume button |
| Automation | The scheduled tasks running inside this container — see Automation & Scheduled Tasks |
| Config | All per-project configuration — see Project Configuration |
| Files | Browse, download and upload files inside the container |
| Browser | Watch — and take over — the browser Claude is driving with Playwright, see The Browser Tab |
Sessions
Claude Code records each conversation on the project's config volume. The Sessions tab lists them with a name or summary, the session id, its working directory, its age, size and message count. Refresh re-reads the list.
Resume opens a new shell tab and runs claude --resume <session-id> for you, with the
project's current permission-mode flags applied. The Overview tab shows the four most recent
sessions with the same Resume action.
Sessions can only be read while the container is running, and they are stored on the config volume — so a Reset deletes them.
Capability Tiles
The Overview tab shows read-only counts of what Claude Code has available inside this container:
| Tile | What is counted |
|---|---|
| Skills | Directories under .claude/skills/ that contain a SKILL.md |
| Agents | .md files under .claude/agents/ |
| Commands | .md files under .claude/commands/ |
| Hooks | Hook handlers configured in .claude/settings.json / settings.local.json |
| Plugins | Installed and enabled Claude Code plugins |
| MCP servers | Servers in ~/.claude.json and in any .mcp.json under /workspace |
Both user scope (/home/claude/.claude) and project scope (/workspace/<folder>/.claude) are
included, and each tile opens a list of what it found.
Triple-C does not edit any of this. Claude Code owns skills, agents, commands, hooks, plugins and MCP servers, and it has good built-in tooling for them. The tiles are a window, not an editor: Manage in terminal opens a terminal in the container so you can use
/agents,/hooks,/plugins,/mcpand friends directly.
The counts are only available while the container is running.
The Browser Tab
When Claude drives a browser with Playwright inside the container, the Browser tab shows you that browser live — and lets you take it over with your own mouse and keyboard.
It is off by default and opted into per project, and it never installs anything on its own. Opening the tab only probes the container, so it can tell you what is missing before you ask for a view; installing Playwright and downloading a browser are separate, labelled buttons that state what they cost before you press them. See What's Inside the Container for why the browser itself is not pre-installed.
Press Start browser view and the pane fills with Playwright's own dashboard, running inside the container and reached over a token-gated listener on your machine's loopback address. Nothing is exposed off the machine.
Watching it while you work
Press Open in own window and the view moves out of the tab into a window of its own — put it on a second monitor, or turn on Keep on top and let it float above the app while you work in a terminal. This is a window change only: the browser and the view keep running throughout, so popping out and back costs nothing and interrupts nothing.
While the view is in its own window the tab shows a placeholder rather than a second copy of it — two viewers would both be able to drive the browser, and two cursors on one page is not useful. Put back in tab, or just closing the window, brings it back.
The window belongs to the view, not to the tab: closing the project's home tab leaves it open, and stopping the view — by pressing Stop, stopping the container, or removing the project — closes it, because a window showing a viewer that no longer exists is worse than no window.
Project Management
Project Status
Each project shows a status glyph paired with a word, so it is readable without relying on colour:
| Glyph | Status | Meaning |
|---|---|---|
○ |
Stopped | Container is not running |
◐ |
Starting / Stopping | Container is transitioning (the glyph pulses) |
● |
Running | Container is active, ready for terminals |
▲ |
Error | Something went wrong (check the toast for detail) |
While a container is starting or stopping, the status line is replaced by the live progress message.
Project Actions
Most actions live in the Project Home header; two live behind the ⋯ overflow menu next to it. The sidebar row carries only the two hover controls.
| Action | Where | When Available | What It Does |
|---|---|---|---|
| Start | Project Home header; sidebar hover control | Stopped | Creates (if needed) and starts the container |
| Stop | Project Home header; sidebar hover control | Running | Stops the container but preserves its state |
| Force stop | Project Home header | Starting / Stopping | Interrupts a transition that is stuck |
| Open Claude Terminal | Project Home header; sidebar hover control; Ctrl+T |
Running | Opens a new Claude Code terminal tab |
| Shell | Project Home header | Running | Opens a bash login shell tab in the container (no Claude Code) |
| Files | Project Home header, and the Files tab | Running | Switches to the Files tab to browse, download and upload files |
| Config | The Config tab | Always | Per-project configuration (most fields need the container stopped) |
| Back up container | ⋯ overflow menu | A container exists | Saves a .tar.gz archive of the container to a location you choose |
| Reset container… | ⋯ overflow menu | Stopped or Error | Destroys the container, snapshot image and both volumes, then recreates from the base image (wipes ~/.claude) — asks first |
| Remove project… | ⋯ overflow menu | Always | Deletes the project, its container, its volumes and its stored credentials — asks first |
Both destructive actions confirm before acting, and the Reset dialog spells out what you lose: your
claude login, anything installed inside the container, and every saved session transcript. Your mounted project folders live on the host and are not touched.
The backup archive includes the Claude config volume, which may contain API keys. Keep it private.
Renaming a Project
Rename a project in its Config tab, under Workspace → Project name. Press Enter to save and leave the field, or Escape to revert. (Double-clicking a terminal tab renames that tab — that is a different thing.)
Container Lifecycle
Containers use a stop/start model. When you stop a container, everything inside it is preserved — installed packages, modified files, downloaded tools. Starting it again resumes where you left off.
Reset container removes the container, its snapshot image and both of its named volumes
(triple-c-home-<project-id> and triple-c-claude-config-<project-id>), then creates a fresh one
from the clean base image. This is destructive: ~/.claude and ~/.claude.json go with the
volumes, so your per-container OAuth login, any skills or agents you installed, your session
transcripts and your scheduled tasks are all lost.
What Reset keeps: your host folders (they are bind mounts and are never touched), the project's
configuration in Triple-C, and anything stored in your OS keychain — including the shared Claude
authentication token. If the project uses that shared token, it re-authenticates by itself after a
Reset; if it relies on claude login, you will need to log in again.
Apart from Remove project…, Reset is the only action that deletes the volumes. Stopping and starting preserves them, and so does the automatic container recreation that happens when you change a setting that affects the container — in both cases your Claude Code configuration survives.
Remove project… deletes everything Reset does, plus the project record itself and its stored credentials.
Container Progress Feedback
When starting, stopping, or resetting a container, progress is shown inline on the project row and in the Project Home header (e.g., "Creating container...", "Starting container..."), so the rest of the app stays usable. If an error occurs it is raised as a toast with the full detail behind a Details disclosure. There is no blocking progress modal.
Permission Modes
Every project has a permission mode that decides how much Claude Code does without asking. It is a segmented control on the Overview tab (and again under Config → Runtime), and it replaces the old Full Permissions on/off switch.
| Mode | What Claude does | What Triple-C passes to claude |
|---|---|---|
| Plan | Proposes a plan and makes no changes | --permission-mode plan |
| Default | Asks before each tool call | (nothing — Claude Code's own default) |
| Accept Edits | Auto-approves file edits; other tools still prompt | --permission-mode acceptEdits |
| Bypass | Auto-approves every tool call | --dangerously-skip-permissions |
New projects start in Default. Projects created before permission modes existed keep behaving the way they did: one that had Full Permissions on becomes Bypass, one that had it off becomes Default.
CAUTION: In Bypass, Claude can execute any command inside the container without asking. The container sandbox limits the blast radius, but think carefully — especially if the container has Docker socket access or reaches services on your network. The Overview tab tells you whether the in-container sandbox is also on.
When a change takes effect
- Terminals — the mode is applied when a terminal is opened, so it affects terminals you open
from then on. A Claude session that is already running keeps the permissions it started with;
close the tab and open a new terminal to change it. The badge on each terminal tab shows the mode
that terminal was launched with (
plan,ask,edits,bypass). - Resumed sessions — a session resumed from the Sessions tab uses the project's current mode.
- Scheduled tasks — these now honour the permission mode too (they previously always ran with
--dangerously-skip-permissions). The mode reaches them through the container's environment, which can only change when the container is recreated, so stop and start the project for a mode change to reach the scheduler.
Scheduled tasks run headless (
claude -p) and cannot answer a permission prompt. In any mode other than Bypass, a task may simply stop early when Claude Code asks for approval. Its run log records which mode it used.
Project Configuration
Open a project's Config tab in Project Home. Configuration is grouped into four sections — Workspace, Model, Access and Runtime — plus Claude instructions and Claude Code settings.
Changes save automatically when a field loses focus, and a Saved / Saving… / Failed indicator in the corner tells you what happened. Most settings can only be changed when the container is stopped; a warning chip appears at the top of the tab if it is running. (The project name and the permission mode can be changed at any time.)
Mounted Folders
Each project mounts one or more host directories into the container. The mount appears at /workspace/<mount-name> inside the container.
- Click Browse to change the host path
- Edit the mount name to control where it appears inside
/workspace/ - Click + Add folder to add more, or Remove to drop one (the last remaining folder cannot be removed)
- Mount names must be unique and use only letters, numbers, dashes, underscores, and dots
SSH Keys
Specify the path to your SSH key directory (typically ~/.ssh). Keys are mounted read-only and copied into the container with correct permissions. This enables git clone via SSH inside the container.
Git Configuration
- Git Name / Email — Sets
git config user.nameanduser.emailinside the container. - Git HTTPS Token — A personal access token (e.g., from GitHub) for HTTPS git operations. Stored securely in your OS keychain — never written to disk in plaintext.
Allow Container Spawning
When enabled, the host Docker socket is mounted into the container so Claude Code can create sibling containers (e.g., for running databases, test environments). This is off by default for security.
Toggling this requires stopping and restarting the container to take effect.
Mission Control
Toggle Mission Control to integrate Flight Control — an AI-first development methodology bundled with Triple-C — into the project. When enabled:
- The bundled Flight Control files are installed into the container
- Flight Control skills are installed to Claude Code's skill directory (
~/.claude/skills/) - Project instructions are appended with Flight Control workflow guidance
- The files are symlinked at
/workspace/mission-control
Available skills include /mission, /flight, /leg, /agentic-workflow, /flight-debrief, /mission-debrief, /daily-briefing, and /init-project.
This setting can only be changed when the container is stopped. Toggling it triggers a container recreation on the next start.
Permission Mode
The Runtime section repeats the permission mode control from the Overview tab — see Permission Modes for what each mode does and when a change takes effect.
Sandbox Mode
Toggles Claude Code's in-container bubblewrap isolation. The Overview tab shows the current state next to the permission mode, because the two together decide how contained a Bypass-mode session really is.
Environment Variables
Add key-value pairs under Access → Environment variables; they are injected into the container. Per-project variables override global variables with the same key.
Reserved prefixes (
ANTHROPIC_,AWS_,GIT_,HOST_,TRIPLE_C_) are filtered out to prevent conflicts, along with the exact names Triple-C manages itself:CLAUDE_INSTRUCTIONS,CLAUDE_CODE_SETTINGS_JSON,CLAUDE_CODE_OAUTH_TOKEN,MISSION_CONTROL_ENABLED,TRIPLE_C_PERMISSION_MODEandMCP_SERVERS_JSON. OtherCLAUDE_CODE_*variables are allowed, so you can set Claude Code feature flags directly (e.g.,CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1).
Port Mappings
Under Access → Port mappings, map host ports to container ports. This is useful when Claude Code starts a web server or other service inside the container and you want to access it from your host browser.
Each mapping specifies:
- Host Port — The port on your machine (1-65535)
- Container Port — The port inside the container (1-65535)
- Protocol — TCP (default) or UDP
Claude Instructions
The Claude instructions editor at the bottom of the Config tab holds per-project instructions for Claude Code. These are written to ~/.claude/CLAUDE.md inside the container and provide project-specific context. If you also have global instructions (in Settings), the global instructions come first, followed by the per-project instructions.
Claude Code Settings
The Claude Code settings editor, also at the bottom of the Config tab, configures Claude Code CLI behavior for this project. These settings control how Claude Code operates inside the container:
| Setting | What It Does |
|---|---|
| TUI Mode | Set to Fullscreen for flicker-free alt-screen rendering (uses CLAUDE_CODE_NO_FLICKER=1) |
| Effort Level | Controls reasoning depth: Low (fast, less thorough), Medium, High (deep reasoning) |
| Focus Mode | Collapses tool output to one-line summaries, showing only the prompt and final response |
| Thinking Summaries | Shows Claude's thinking process as summaries during responses |
| Session Recap | Provides context when returning to a session after being away |
| Auto-Scroll Disabled | Disables auto-scroll when in fullscreen TUI mode |
| Env Scrub | Strips credentials from subprocess environments for security |
| Prompt Caching (1h) | Enables 1-hour prompt cache TTL instead of the default 5 minutes |
Per-project settings override global defaults set in Settings. If all settings are at their defaults, no configuration is injected.
These settings map to Claude Code environment variables and
~/.claude/settings.jsonentries. Changes require stopping and restarting the container to take effect.
MCP Servers
Triple-C no longer manages MCP servers itself. Configure them with Claude Code's own tooling from a terminal inside the container:
claude mcp add— register a serverclaude mcp list— show configured serversclaude mcp remove— delete a server/mcp— slash command inside a Claude Code session for MCP status and authentication- A project-level
.mcp.jsonin/workspace— checked into your repo and shared with anyone who opens the project
Your MCP configuration persists across container stop/start because ~/.claude.json and ~/.claude live on named Docker volumes. A Reset wipes them, so you would need to re-add your servers afterwards.
Shared Claude Authentication
Instead of running claude login separately in every container, you can authenticate once and
share the result across projects. Claude Code's claude setup-token mints a long-lived token
(roughly a year) that Triple-C stores in your OS keychain and injects into containers as
CLAUDE_CODE_OAUTH_TOKEN.
This lives in the sidebar under Settings → Claude Authentication.
Signing in
- Start at least one project whose container is running — the flow borrows that container as a place to run the CLI. The token it produces is global, not tied to that project.
- Start the sign-in. Triple-C runs
claude setup-tokeninside the container and streams its output. - Claude Code prints an authorization URL. Open it, sign in, and Anthropic's page gives you a code to copy — this flow finishes on an Anthropic-hosted page, not a local callback.
- Paste the code back into Triple-C. The token is captured and written straight to the keychain.
The code is long and easy to truncate. If Anthropic refuses it, the dialog says so and lets you paste another one without restarting the sign-in — the CLI is still waiting. After a few refusals the flow gives up and reports it rather than sitting there.
Only one sign-in can run at a time, and the whole flow times out after 15 minutes. A long-lived
token requires a Claude subscription; without one, setup-token finishes without printing a token
and nothing is stored.
How the token is used
- It is injected only into projects whose backend is Anthropic — it means nothing to Bedrock, Ollama, llama.cpp or an OpenAI-compatible gateway.
- Each project can opt out under Config → Model ("Use the shared Claude token"). Projects are
opted in by default, so a single sign-in covers your whole fleet; opt a project out if you
want it pinned to its own
claude loginidentity. CLAUDE_CODE_OAUTH_TOKENis reserved — you cannot set it yourself as a custom environment variable, because a hand-set value would silently outrank the stored token.- A container picks the token up when it is next started: acquiring, re-acquiring, revoking or opting out changes an internal marker that triggers a container recreation on the next start. Restart your Anthropic-backend containers after signing in.
Revoking
Revoking deletes the token from your keychain. Containers keep the value they were given until each is next started, at which point the same recreation clears the variable.
The token is never shown in the app, never written to a log, and never sent to the frontend. While
setup-tokenis running, its output is filtered so anything resembling ansk-ant-secret is masked before it reaches the screen — including a secret split across two chunks of output.
Opening URLs in Your Browser (URL Relay)
There is no browser inside the container and no screen to put one on. Any tool that tries to open a web page therefore fails, usually with something unhelpful like "Couldn't find a suitable web browser!". The URL relay fixes that: when a command inside the container asks for a browser, the URL is handed to your browser on the host.
Nothing is displayed or forwarded from the container — only the URL travels.
It is always on and needs no configuration.
What you see
A small bar appears at the top of the terminal reading "Container asked to open a URL", with the URL and an Open button. Click Open and the page loads in your normal browser, signed in as you. The prompt disappears on its own after 30 seconds if you ignore it.
Triple-C asks rather than opening pages by itself. The container is sandboxed code — some of it written by Claude a minute ago — and silently making your logged-in browser visit a URL it chose is not something to hand over automatically. One click keeps that decision yours.
Which commands benefit
Anything that opens a browser to authenticate or to show you a page:
| Command | What it wanted a browser for |
|---|---|
gh auth login |
GitHub device / OAuth login |
aws sso login |
AWS IAM Identity Center login |
gcloud auth login |
Google Cloud login |
az login |
Azure login |
vercel login, netlify login, fly auth login, heroku login, wrangler login |
Vendor CLI logins |
npm login, supabase login, doctl auth init |
Token / device flows |
xdg-open <url> in any script |
Opening a page directly |
python3 -m webbrowser <url> |
Anything using Python's webbrowser module |
Under the hood the container provides a stand-in browser at /usr/local/bin/triple-c-open,
installed under all the names tools look for — xdg-open, sensible-browser, www-browser,
x-www-browser, gnome-open, gvfs-open, kde-open, open — and as the $BROWSER
environment variable, which most of the CLIs above consult first. You can also call
triple-c-open <url> yourself.
It works even when the command is run by Claude Code rather than typed by you: the relay talks to the terminal directly, not through the command's output, so being nested inside a tool call does not break it.
When no terminal is attached
The relay rides on the terminal session. If nothing is attached to the container, there is nothing to relay through:
- Scheduled tasks (Automation tab) run from cron with no terminal at all.
- A shell you opened with your own
docker exec, outside Triple-C.
In those cases the relay does not hang or wait. It prints the URL in plain text and returns immediately:
triple-c-open: no Triple-C terminal attached — cannot reach the host browser.
triple-c-open: open this URL manually:
https://github.com/login/device?user_code=WXYZ-1234
For a scheduled task that text lands in the task log (Project Home → Automation → Logs), so you can still finish the login yourself afterwards. Practically speaking: don't expect an unattended scheduled task to complete an interactive browser login. Authenticate once from a terminal session — the credentials persist in the project's config volume — and let the scheduled runs use them.
Security
Requests coming out of the container are treated as untrusted input, because that is what they are:
- Only
http://andhttps://are ever opened.file://,javascript:,data:and every custom protocol handler your OS has registered are rejected outright. A container that could make the host open arbitrary URI schemes would have a way out of the sandbox. - URLs with embedded credentials (
https://github.com@evil.example/) are rejected — they misrepresent which site you are about to visit. - Control characters, whitespace and oversized payloads are rejected before parsing, so the relay cannot be used to smuggle terminal escape sequences into the UI.
- The URL is shown to you in its normalized form: what the prompt displays is exactly what opens.
- Prompts are rate-limited (a handful per ten seconds, with repeats of the same URL collapsed), so a runaway loop in the container cannot bury the interface.
The relay only asks. Nothing opens without your click.
Browser Logins Inside the Container (Auth Bridge)
Some CLIs log you in by opening a browser and waiting for the browser to call back to a temporary
web server they started on localhost. claude login, aws sso login and Concourse's
fly login all work this way. When the CLI runs inside a container, that localhost is the
container's — the browser on your host calls back into nothing and the login hangs forever.
The Auth Bridge fixes this. It is opt-in per project and off by default.
What it does
- Every couple of seconds it looks inside the container for programs listening on the container's loopback address, and binds the same port number on your host. That is the whole trick: the redirect URL the login provider was handed resolves correctly on both sides.
- Connections are carried into the container over the Docker API, which keeps working on Docker Desktop where container IP addresses are not reachable from the host.
- It follows whichever address family the container program actually used. This matters in
practice: Node resolves
localhostto IPv6 first on Linux, soclaude loginfrequently listens on::1and nothing else. - Ports you have already configured as port mappings are left alone. If a host port is already taken, the bridge reports a conflict and leaves it alone rather than fighting for it — it will retry on a later pass.
- The bridge is entirely host-side, so turning it on or off never recreates the container. It stops by itself when the container stops.
Security
The host side binds loopback only — 127.0.0.1 and [::1], never a wildcard address. Nothing
on your network can reach a bridged port. Within your own machine, though, a bridged port is
reachable by any local process for as long as the in-container listener exists, and the services
behind it are unauthenticated: they bound loopback precisely because they expected to be reachable
from nowhere else. Only container programs that bound loopback are bridged; anything listening on
all interfaces is deliberately ignored (publishing those is what port mappings are for).
Leave it off unless you need it, and it will not be running.
AWS Bedrock Configuration
To use Claude via AWS Bedrock instead of Anthropic's API, set Backend to Bedrock under Config → Model.
Authentication Methods
| Method | Fields | Use Case |
|---|---|---|
| Static keys | Access Key ID, Secret Access Key, Session Token (optional) | Direct credentials — simplest setup |
| Named profile | AWS Profile name | Uses ~/.aws/config and ~/.aws/credentials on the host |
| Bearer token | Bearer Token | Temporary bearer token authentication |
With Named profile, the SSO session is validated before Claude Code launches, so an expired session is caught at the start of a terminal rather than mid-task.
Additional Bedrock Settings
- AWS Region — Required. The region where your Bedrock models are deployed (e.g.,
us-east-1). - Model ID — Optional. Override the default Claude model (e.g.,
anthropic.claude-sonnet-4-20250514-v1:0). - Service tier — Optional. Selects a Bedrock service tier.
Global AWS Defaults
In Settings > AWS Configuration, you can set defaults that apply to all Bedrock projects:
- AWS Config Path — Path to your
~/.awsdirectory. Click Detect to auto-find it. - Default Profile — Select from profiles found in your AWS config.
- Default Region — Fallback region for projects that don't specify one.
Per-project settings always override these global defaults.
Ollama Configuration
To use Claude Code with a local or remote Ollama server, set Backend to Ollama under Config → Model.
Settings
- Base URL — The URL of your Ollama server. Defaults to
http://host.docker.internal:11434, which reaches a locally running Ollama instance from inside the container. For a remote server, use its IP or hostname (e.g.,http://192.168.1.100:11434). - Model ID — Required. The model to use (e.g.,
qwen3.5:27b). The model must be pulled in Ollama before use — runollama pull <model>or use it via Ollama cloud so it is available when the container starts. - Background model — Optional. See Model Aliases and Background Calls. Leave blank to reuse the Model ID above.
Global defaults for all three live under Settings → Backends → Ollama Configuration and are used whenever the matching per-project field is blank.
How It Works
Ollama natively implements the Anthropic Messages API at POST /v1/messages, which is the only thing Claude Code ever sends. Triple-C sets ANTHROPIC_BASE_URL to point Claude Code at your Ollama server instead of Anthropic's API. The ANTHROPIC_AUTH_TOKEN is set to ollama (required by Claude Code but not used for actual authentication). The ANTHROPIC_DEFAULT_*_MODEL aliases are pinned to your model — see Model Aliases and Background Calls.
Note: Ollama support is best-effort. Claude Code is designed for Anthropic models, so some features (tool use, extended thinking, prompt caching, etc.) may not work as expected with non-Anthropic models.
Important: The model must already be available in Ollama before starting the container. If using a local Ollama instance, pull the model first with
ollama pull <model-name>. If using Ollama's cloud service, ensure the model has been used at least once so it is cached.
llama.cpp Configuration
To use Claude Code with a local or remote llama-server (from llama.cpp), set Backend to llama.cpp under Config → Model.
llama-server implements the Anthropic Messages API natively — POST /v1/messages and POST /v1/messages/count_tokens — so Claude Code talks to it directly, with no translation layer in between.
Settings
- Base URL — The URL of your
llama-server. Defaults tohttp://host.docker.internal:8080; 8080 isllama-server's own default port (--port PORT | port to listen (default: 8080)). For a remote server, use its IP or hostname. - Model ID — The model
llama-serveris serving. Allama-serverprocess serves one model, so this is mostly the id Claude Code reports — but it is also what the model aliases are pinned to, so setting it matters. - Background model — Optional. See Model Aliases and Background Calls. Leave blank to reuse the Model ID above.
Global defaults for all three live under Settings → Backends → llama.cpp Configuration and are used whenever the matching per-project field is blank.
Starting llama-server
llama-server -m /path/to/model.gguf --port 8080 --host 0.0.0.0
--host 0.0.0.0 matters: llama-server binds 127.0.0.1 by default, which the container cannot reach through host.docker.internal.
How It Works
Triple-C sets ANTHROPIC_BASE_URL to your llama-server, and ANTHROPIC_AUTH_TOKEN to the placeholder llama.cpp. llama-server only checks the Authorization header when it was started with --api-key (default: none), so the value is ignored in the usual case — but Claude Code requires some credential to be present, so one is always sent.
Note: llama.cpp support is best-effort. Claude Code is designed for Anthropic models, so some features (tool use, extended thinking, prompt caching, etc.) may not work as expected with non-Anthropic models.
OpenAI Compatible Configuration
To route Claude Code through a gateway, set Backend to OpenAI Compatible under Config → Model.
The name is misleading, and the distinction matters. Claude Code only ever sends
POST /v1/messages?beta=truein Anthropic Messages format toANTHROPIC_BASE_URL. It never calls OpenAI's/v1/chat/completions. So this backend requires an endpoint that implements the Anthropic Messages API — LiteLLM does, and works. A server that exposes only an OpenAI-compatible API (plain vLLM, text-generation-inference, LocalAI, OpenRouter, …) will not work here; put an Anthropic-shaped gateway such as LiteLLM in front of it. For Ollama and llama.cpp, use their own backends — both implement/v1/messagesnatively.(The backend name is kept as-is so existing projects keep working.)
Settings
- Base URL — The URL of your gateway. Defaults to
http://host.docker.internal:4000, LiteLLM's default port (adjust to match your server's address and port). - API Key — Optional. The API key for your endpoint, if authentication is required. Stored securely in your OS keychain.
- Model ID — Optional. Override the model to use.
- Background model — Optional. See Model Aliases and Background Calls. Leave blank to reuse the Model ID above.
Global defaults for the base URL, model and background model live under Settings → Backends → OpenAI Compatible Configuration.
How It Works
Triple-C sets ANTHROPIC_BASE_URL to point Claude Code at your gateway. If an API key is provided, it is set as ANTHROPIC_AUTH_TOKEN.
Note: OpenAI Compatible support is best-effort. Claude Code is designed for Anthropic models, so some features (tool use, extended thinking, prompt caching, etc.) may not work as expected when routing to non-Anthropic models through the endpoint.
Model Aliases and Background Calls
Claude Code has four model aliases — opus, sonnet, haiku and fable. Left alone they resolve
to Anthropic's model IDs. A local server has never heard of those IDs, so every call that goes
through an alias fails, usually with no visible error.
The one that bites hardest is haiku: ANTHROPIC_DEFAULT_HAIKU_MODEL is documented as "Model ID
that the haiku alias resolves to, also used for background functionality" — conversation titles,
summaries, and other out-of-band work. If it is wrong, those quietly stop happening.
So for every backend that points at a custom endpoint — Ollama, llama.cpp and OpenAI Compatible — Triple-C sets all four:
| Variable | Value |
|---|---|
ANTHROPIC_DEFAULT_OPUS_MODEL |
your configured Model ID |
ANTHROPIC_DEFAULT_SONNET_MODEL |
your configured Model ID |
ANTHROPIC_DEFAULT_HAIKU_MODEL |
your Background model, or the Model ID if that is blank |
ANTHROPIC_DEFAULT_FABLE_MODEL |
your configured Model ID |
Leaving Background model blank is the right default. A local server almost always serves one model, and pointing every alias at it is what makes background work succeed.
Set Background model only if you serve a second, smaller model you would rather spend on titles and summaries. It moves the Haiku alias alone; the other three still follow Model ID. It is available per-project (Config → Model) and globally (Settings → Backends), with the usual per-project-overrides-global rule.
Notes:
- These variables are not set for the Anthropic or Bedrock backends. Those reach servers that genuinely host the Anthropic model IDs, so Claude Code's own defaults are correct.
- All four names are reserved — you cannot set them yourself as custom environment variables.
- Changing a model or a Background model recreates the container on the next start, because environment variables can only change at creation time.
ANTHROPIC_SMALL_FAST_MODEL, the deprecated predecessor of the Haiku variable, is not used.
Settings
Access global settings via the Settings tab in the sidebar. The panel is a set of collapsible sections: General, Claude Authentication, Backends, Container, Certificates, Git / SSH, Tools and Updates.
Claude Authentication
Acquire or revoke the shared Claude authentication token — see Shared Claude Authentication.
Docker Settings
- Docker Status — Connection status to the Docker daemon.
- Image Source — Where to get the sandbox container image (Registry, Local Build, or Custom).
- Pull / Build Image — Download or build the image. Progress streams in real time.
- Refresh — Re-check Docker and image status.
Container Timezone
Set the timezone for all containers (IANA format, e.g., America/New_York, Europe/London, UTC). Auto-detected from your host on first launch. This affects scheduled task timing inside containers.
Global Claude Instructions
Instructions applied to all projects. Written to ~/.claude/CLAUDE.md in every container, before any per-project instructions.
Global Environment Variables
Environment variables applied to all project containers. Per-project variables with the same key take precedence.
Default SSH Key Directory
Path to your SSH key directory (typically ~/.ssh). This is mounted into all containers that don't have a per-project SSH path set. Per-project SSH paths take precedence.
Corporate CA Certificate
If your organisation's network inspects TLS (a corporate proxy, a VPN that terminates HTTPS at the
edge), containers need your organisation's root certificate or every HTTPS call inside them
fails — npm install, pip, git clone over HTTPS, curl, the browser-view pane, and Claude
Code's own calls to the API.
Point this at either a single certificate file or a folder of them. It is mounted read-only into every container and applied on every start, so it survives container recreation, base-image migration and Reset — unlike a certificate you install by hand inside a running container, which is lost the first time any of those happens.
The status line under the field tells you how many certificates were found and the names they will
be installed as inside the container. That rename matters: the container's trust store only reads
files ending in .crt, so a .pem is renamed rather than merely copied, which is the step that is
easiest to get wrong by hand.
Inside the container the certificate is trusted by:
| Consumer | How |
|---|---|
| curl, git, apt, wget | the system trust store (update-ca-certificates) |
| Node, npm, Claude Code itself | NODE_EXTRA_CA_CERTS |
| Python, pip, requests | REQUESTS_CA_BUNDLE and SSL_CERT_FILE |
| Chrome / Chromium (browser view) | its own NSS database at ~/.pki/nssdb |
A per-project override lives in Project Home → Config → Access; leave it blank to use this global setting. Changing either recreates the project's container on its next start — replacing the certificate file in place counts as a change, so a rotated CA is picked up too.
Default Git Name / Email
Sets git user.name and git user.email inside all containers. Per-project Git Name / Email settings take precedence. This is useful so you don't have to set the same name and email on every project.
Claude Code Settings (Global Defaults)
Default Claude Code CLI settings applied to all projects. See Claude Code Settings in the Project Configuration section for a description of each setting. Per-project settings override these global defaults.
Web Terminal
Enable remote access to your project terminals from any device on the local network (tablets, phones, other computers).
- Toggle — Click ON/OFF to start or stop the web terminal server.
- URL — When running, shows the full URL including the access token. Click Copy URL to copy it to your clipboard, then open it in a browser on your tablet or phone.
- Token — An access token is auto-generated on first enable. Click Copy to copy the token, or Regenerate to create a new one (this disconnects existing web sessions).
- Port — Defaults to 7681. Configurable in
settings.jsonif needed.
The web terminal server auto-starts on app launch if it was previously enabled, and stops when the app closes.
Updates
- Current Version — The installed version of Triple-C.
- Auto-check — Toggle automatic update checks (every 24 hours).
- Check now — Manually check for updates.
When an update is available, a pulsing Update button appears in the top bar. Click it to see release notes and download links.
Web Terminal (Remote Access)
The web terminal lets you access your running project terminals from a tablet, phone, or any other device on the local network — no app installation required, just a web browser.
Setup
- Go to Settings in the sidebar.
- Find the Web Terminal section and click the toggle to ON.
- A URL appears (e.g.,
http://192.168.1.100:7681?token=...). Click Copy URL. - Open the URL in a browser on your tablet or other device.
Using the Web Terminal
The web terminal UI mirrors the desktop app's terminal experience:
- Project picker — Select a running project from the dropdown at the top.
- Claude / Bash buttons — Open a new Claude Code or bash session for the selected project.
- Tab bar — Switch between multiple open sessions. Click the x on a tab to close it.
- Input bar — A text input at the bottom optimized for mobile/tablet keyboards. Characters are sent immediately without waiting for autocomplete. Helper buttons for Enter, Tab, and ^C (Ctrl+C) are provided for keys that are awkward on virtual keyboards.
- Scroll to bottom — A floating arrow button appears when you scroll up, letting you jump back to the latest output.
Security
- Access requires a token in the URL query string. Without the correct token, connections are rejected.
- The token is auto-generated (32 bytes, base64url-encoded) and can be regenerated at any time from Settings.
- The server only listens on port 7681 (configurable) — make sure this port is not exposed to the public internet.
- All sessions opened from a browser tab are automatically cleaned up when the tab is closed or the WebSocket disconnects.
Tips
- Bookmark the URL on your tablet for quick access.
- The web terminal works best in landscape orientation on tablets.
- If the connection drops (e.g., Wi-Fi interruption), the web terminal auto-reconnects after 2 seconds.
- Regenerating the token invalidates all existing browser sessions — you'll need to update bookmarks with the new URL.
Terminal Features
Multiple Sessions
You can open multiple terminal sessions (even for the same project). Each session gets its own tab in the main tab strip, alongside any open Project Home tabs. Click a tab to switch, or click the × on a tab to close it. Tabs show the project name (or a custom session name if you set one), with a "(bash)" suffix for shell sessions and a badge for the permission mode the session was launched with.
Right-click a terminal tab for Rename tab, Reset name, Open project home and Close tab; double-click it to rename inline.
Bash Shell Sessions
In addition to Claude Code terminals, you can open a plain bash login shell in any running container by clicking the Shell button. This is useful for manual inspection, package installation, debugging, or running commands that don't need Claude Code.
URL Detection
When Claude Code prints a long URL (e.g., during claude login), Triple-C detects it and shows a toast notification at the top of the terminal with an Open button. Clicking it opens the URL in your default browser. The toast auto-dismisses after 30 seconds.
Shorter URLs in terminal output are also clickable directly.
Copying and Pasting
Use Ctrl+Shift+C (or Cmd+C on macOS) to copy selected text from the terminal, and Ctrl+Shift+V (or Cmd+V on macOS) to paste. This follows standard terminal emulator conventions since Ctrl+C is reserved for sending SIGINT.
Clipboard Support (OSC 52)
Programs inside the container can copy text to your host clipboard. When a container program uses xclip, xsel, or pbcopy, the text is transparently forwarded to your host clipboard via OSC 52 escape sequences. No additional configuration is required — this works out of the box.
Image Paste
You can paste images from your clipboard into the terminal (Ctrl+V / Cmd+V). The image is uploaded to the container as /tmp/clipboard_<timestamp>.png and the file path is injected into the terminal input so Claude Code can reference it. A toast notification confirms the upload.
Jump to Current
When you scroll up in the terminal to review previous output, a Jump to Current button appears in the bottom-right corner. Click it to scroll back to the latest output.
Files
The Files tab of Project Home browses inside a running container. You can:
- Browse the container filesystem, starting at
/workspace, with breadcrumb navigation - Download any file to your host machine via the Download button on each file entry
- Upload file from your host into the current container directory
- Refresh the directory listing at any time
The listing shows file names, sizes, and modification dates.
Terminal Rendering
The terminal uses WebGL for hardware-accelerated rendering of the active tab. Inactive tabs fall back to canvas rendering to conserve GPU resources. The terminal automatically resizes when you resize the window.
Automation & Scheduled Tasks
Each container can run Claude Code on a schedule — recurring or one-time — through a small
scheduler called triple-c-scheduler that lives inside the image. Tasks run as separate,
headless Claude Code invocations (claude -p "<your prompt>") driven by cron.
The Automation tab in Project Home is the place to watch and control them; tasks are created
from inside the container with the triple-c-scheduler CLI.
The Automation Tab
With the container running, the Automation tab lists every task the scheduler knows about. For each one you get its name, whether it is recurring or one-time, its cron expression or scheduled time, and when it last ran. For each task you can:
| Control | What it does |
|---|---|
| Toggle | Enable or disable the task without deleting it |
| Run now | Trigger the task immediately, outside its schedule |
| Log | Show the tail of that task's run log (last 200 lines) |
| Remove | Delete the task, after a confirmation |
Refresh re-reads everything from the container.
When tasks finish they leave notifications. If any are waiting, a panel appears at the top of the tab listing each one with its task name, whether it succeeded or failed, how long ago it ran and a summary. Clear all dismisses them. The Overview tab also shows a notification count and the next few scheduled tasks.
Permission mode
Scheduled runs use the project's permission mode — they no longer always run
with --dangerously-skip-permissions. Because the mode travels into the container as an
environment variable, stop and start the project after changing it for the scheduler to see the
change. Remember that a headless run cannot answer a permission prompt, so in any mode other than
Bypass a task may stop early when Claude Code asks for approval; the run log records the mode
that was used.
Creating Tasks (In the Container)
There is no "add task" form in the app. Create tasks from a terminal in the container — either type the commands yourself in a Shell session, or just ask Claude to do it.
Create a Recurring Task
triple-c-scheduler add --name "daily-review" --schedule "0 9 * * *" --prompt "Review open issues and summarize"
Create a One-Time Task
triple-c-scheduler add --name "migrate-db" --at "2026-03-05 14:00" --prompt "Run database migrations"
One-time tasks automatically remove themselves after execution.
Manage Tasks From the CLI
The CLI still works, and does the same things the Automation tab does:
triple-c-scheduler list # List all tasks
triple-c-scheduler enable --id abc123 # Enable a task
triple-c-scheduler disable --id abc123 # Disable a task
triple-c-scheduler remove --id abc123 # Delete a task
triple-c-scheduler run --id abc123 # Trigger a task immediately
triple-c-scheduler logs --id abc123 # View logs for a task
triple-c-scheduler logs --tail 20 # View last 20 log entries (all tasks)
triple-c-scheduler notifications # View completion notifications
triple-c-scheduler notifications --clear # Clear notifications
Cron Schedule Format
Standard 5-field cron: minute hour day-of-month month day-of-week
| Example | Meaning |
|---|---|
*/30 * * * * |
Every 30 minutes |
0 9 * * 1-5 |
9:00 AM on weekdays |
0 */2 * * * |
Every 2 hours |
0 0 1 * * |
Midnight on the 1st of each month |
Working Directory
By default, tasks run in /workspace. Use --working-dir to specify a different directory:
triple-c-scheduler add --name "test" --schedule "0 */6 * * *" --prompt "Run tests" --working-dir /workspace/my-project
Scheduled tasks live on the project's config volume, so a Reset deletes them along with everything else on that volume.
Keyboard Shortcuts
Application
| Shortcut | Action |
|---|---|
| Ctrl+T | Open a new Claude terminal for the current project (nothing happens unless its container is running) |
| Ctrl+Shift+W | Close the active tab |
| Ctrl+Tab | Switch to the next tab |
| Ctrl+Shift+Tab | Switch to the previous tab |
| Ctrl+1 … Ctrl+9 | Jump to the first through ninth tab |
| Ctrl+Shift+← / Ctrl+Shift+→ | Move the active tab one place along the strip (the mouse equivalent is dragging it) |
Why Ctrl+Shift+W and not Ctrl+W?
Ctrl+Wis readline'skill-word— it deletes the word before the cursor, and it is used constantly in the terminal this app is built around. Binding it to "close tab" would make the shell unusable, so Triple-C deliberately leavesCtrl+Walone.
In the Terminal
| Shortcut | Action |
|---|---|
| Ctrl+Shift+C | Copy the selection, with trailing whitespace trimmed |
| Ctrl+Shift+Alt+C | Copy the selection exactly as-is |
| Ctrl+Shift+V | Paste |
| Ctrl+V | Paste an image from the clipboard into the container |
| Ctrl+Shift+M | Toggle speech-to-text recording (when enabled) |
Everything else goes straight through to the program running in the container.
What's Inside the Container
The sandbox container (Ubuntu 24.04) comes pre-installed with:
| Tool | Version | Purpose |
|---|---|---|
| Claude Code | Latest | AI coding assistant (the tool being sandboxed) |
| Node.js | 22 LTS | JavaScript/TypeScript development |
| pnpm | Latest | Fast Node.js package manager |
| Python | 3.12 | Python development |
| uv | Latest | Fast Python package manager |
| ruff | Latest | Python linter/formatter |
| Rust | Stable | Rust development (via rustup) |
| Docker CLI | Latest | Container management (when spawning is enabled) |
| git | Latest | Version control |
| GitHub CLI (gh) | Latest | GitHub integration |
| AWS CLI | v2 | AWS services and Bedrock |
| ripgrep | Latest | Fast code search |
| build-essential | — | C/C++ compiler toolchain |
| openssh-client | — | SSH for git and remote access |
The container also includes clipboard shims (xclip, xsel, pbcopy) that forward copy operations to the host via OSC 52, a browser shim (triple-c-open, installed as xdg-open, sensible-browser, www-browser, x-www-browser and $BROWSER) that relays URLs to your host browser — see Opening URLs in Your Browser — and an audio shim (rec, arecord) for future voice mode support.
It also ships the system libraries a browser needs to run (libnss3, libgbm1, libatk*, libasound2t64, libcups2t64, libpango, libdrm2, fonts, and the rest of the set Playwright asks for). So npx playwright install chromium gives you a browser that actually starts. Before these were baked in, that download succeeded and the browser then died with "Host system is missing dependencies: libnss3.so", which is why sudo apt install google-chrome-stable looked like the cure — apt was quietly installing the same libraries as Chrome's own dependencies.
The browsers themselves are not pre-installed — they are hundreds of megabytes and tied to the Playwright version you use. Install one with the Browser tab's setup buttons, or npx playwright install chromium in a terminal. They land in ~/.cache/ms-playwright, which is on the home volume, so a browser survives container recreation and base-image migration and is only lost on a project Reset.
If your project's container was created from an older base image, it won't have the libraries — the Browser tab's install action detects that and installs them for you first, and says so while it does. That install lives in the container's writable layer, so it is undone by a Reset and by a base-image migration; migrating the project onto the current base image is what picks the libraries up for good.
You can install additional tools at runtime with sudo apt install, pip install, npm install -g, etc. Installed packages persist across container stops (but not across resets).
Claude Code Tips
These features are built into Claude Code and work inside Triple-C containers with no extra configuration:
| Feature | How to Use |
|---|---|
| Focus Mode | Run /focus or press Ctrl+O in the terminal to toggle collapsed tool output |
| Session Recap | Run /recap to get a summary of what happened in the current session |
| Session Color | Run /color red (or any color) to color-code your terminal prompt bar |
| Recurring Tasks | Run /loop 5m check the deploy to repeat a prompt every 5 minutes |
| Interactive Lessons | Run /powerup to learn Claude Code features with animated demos |
| Team Onboarding | Run /team-onboarding to generate a teammate ramp-up guide |
| Bedrock Setup | Select "3rd-party platform" on the login screen for an interactive Bedrock setup wizard |
| Vertex AI Setup | Select "3rd-party platform" on the login screen for an interactive Vertex AI setup wizard |
| MCP Elicitation | MCP servers can now request structured user input mid-task — works automatically |
Troubleshooting
Docker is "Not Available"
- Is Docker running? Start Docker Desktop or the Docker daemon (
sudo systemctl start docker). - Permissions? On Linux, ensure your user is in the
dockergroup or the socket is accessible. - Custom socket path? If your Docker socket is not at the default location, set it in Settings. The app expects
/var/run/docker.sockon Linux/macOS or//./pipe/docker_engineon Windows.
Image is "Not Found"
- Click Pull Image or Build Image in Settings > Docker.
- If pulling fails, check your network connection and whether you can reach the registry.
- Try switching to Local Build as an alternative.
Container Won't Start
- Check that the Docker image is "Ready" in Settings.
- Verify that the mounted folder paths exist on your host.
- Read the error toast — the full message is behind its Details disclosure.
OAuth Login URL Not Opening
- Triple-C detects long URLs printed by
claude loginand shows a toast with an Open button. - If the toast doesn't appear, try scrolling up in the terminal — the URL may have already been printed.
- You can also manually copy the URL from the terminal output and paste it into your browser.
"Couldn't find a suitable web browser" / a Command Won't Open a Page
The URL relay should catch this. If a command still complains, check from a terminal session in that project:
echo "$BROWSER" # /usr/local/bin/triple-c-open
triple-c-open https://example.com/ # should raise the prompt in the terminal
If $BROWSER is empty or triple-c-open is missing, the container is running an older image.
Rebuild it (Project Home → Reset, or pull/build the image again from Settings) — the relay is
part of the container image, not something the app can inject into a running container.
If you see "no Triple-C terminal attached", the command is running somewhere with no terminal —
a scheduled task, or a shell you opened with your own docker exec. The URL is printed instead;
copy it into your browser. See
When no terminal is attached.
If the prompt says the URL was refused, the command asked for a scheme the relay will not open on
your machine (anything that isn't http/https).
A Browser Login Never Completes
You opened the URL, signed in successfully, and the CLI in the terminal is still waiting. The
callback from your browser is landing on your host's localhost while the CLI is listening on the
container's. Enable the
Auth Bridge for that project and try again.
For Claude specifically, the simpler answer is usually Shared Claude Authentication, which finishes on an Anthropic-hosted page and needs no callback at all.
A Scheduled Task Stopped Part-Way Through
Scheduled tasks run headless and cannot answer a permission prompt. If the project is not in Bypass mode, a task will stop when Claude Code asks for approval. Check the task's Log in the Automation tab — it records the permission mode the run used.
A Permission Mode Change Didn't Apply
- In a terminal: the mode is set when the terminal opens. Close the tab and open a new one.
- For scheduled tasks: the mode reaches the scheduler through the container's environment. Stop the project and start it again.
File Permission Issues
- Triple-C automatically remaps the container user's UID/GID to match your host user, so files created inside the container should have the correct ownership on your host.
- If you see permission errors, try resetting the container: stop it, then choose Reset container from the ⋯ menu in the Project Home header. Note that this wipes
~/.claude.
Settings Won't Save
- Most project settings can only be changed when the container is stopped. Stop the container first, make your changes, then start it again.
- Some changes (like toggling Docker access, Mission Control, or changing mounted folders) trigger an automatic container recreation on the next start.
"Failed to install Anthropic marketplace" Error
If Claude Code shows "Failed to install Anthropic marketplace - Will retry on next startup" repeatedly, the marketplace metadata in ~/.claude.json may be corrupted. To fix this, open a Shell session in the project and run:
cp ~/.claude.json ~/.claude.json.bak && jq 'with_entries(select(.key | startswith("officialMarketplace") | not))' ~/.claude.json.bak > ~/.claude.json
This backs up your config and removes the corrupted marketplace entries. Claude Code will re-download them cleanly on the next startup.