Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## ADDED Requirements
## MODIFIED Requirements

### Requirement: Help flag prints usage and exits

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,21 @@ The container image SHALL ship with `tfenv` on the default PATH so users can fet
Host credentials (files or env vars) SHALL NOT reach the container unless the user explicitly opts in per-run. `run.sh` defaults to no credential mounts and no token env forwarding. Opt-ins are granted via dedicated flags:

- `--aws`: mount `~/.aws/config` at `/root/.aws/config:ro` and, when present, `~/.aws/sso/` at `/root/.aws/sso:ro`; forward `AWS_PROFILE`, `AWS_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` when set on the host.
- `--gh`: forward `GH_TOKEN` or `GITHUB_TOKEN` when set on the host. If neither
- `--gh`: discover the host token from `GH_TOKEN` or `GITHUB_TOKEN`; if neither
is set, `run.sh` SHALL attempt to retrieve the active token by running
`gh auth token` on the host and forward the result as `GH_TOKEN`. If `gh` is
not on the host PATH or the command fails, `run.sh` SHALL continue silently
without a token.
`gh auth token` on the host. A discovered token SHALL be provided only to the
per-session auth proxy sidecar (see capability `gh-auth-proxy`) — it SHALL
NOT be forwarded into the agent container, which instead receives the
placeholder `GH_TOKEN=claude-docker-proxy` and reaches GitHub through the
sidecar. If `gh` is not on the host PATH or the command fails, `run.sh`
SHALL continue silently without a token and without a sidecar.
- `--gh-direct`: legacy escape hatch. Same token discovery as `--gh`, but the
token is forwarded directly into the agent container as `GH_TOKEN` and no
sidecar is started. Intended for custom-hostname GitHub (Enterprise Server /
`*.ghe.com`) and hosts that cannot run the sidecar. Passing `--gh` and
`--gh-direct` together SHALL exit with an error. The mode SHALL surface as
a distinct `gh-direct` entry in `CLAUDE_DOCKER_FLAGS` so the statusline tag
distinguishes it from proxied `gh`.
- `--glab`: mount the platform-appropriate glab config dir — `~/Library/Application Support/glab-cli` on macOS, `~/.config/glab-cli` on Linux — at `/root/.config/glab-cli:ro`; forward `GITLAB_TOKEN` when set on the host.
- `--tfe`: when present on the host, mount `~/.terraform.d/credentials.tfrc.json` at `/root/.terraform.d/credentials.tfrc.json:ro`; forward `TF_TOKEN_app_terraform_io` when set on the host. Targets `app.terraform.io` (HCP Terraform); self-hosted Terraform Enterprise hostnames and other `TF_TOKEN_<host>` variables are out of scope for this opt-in.

Expand Down Expand Up @@ -74,25 +84,40 @@ All credential bind-mounts SHALL be read-only so a compromised container cannot
- **THEN** `glab auth status` reports "logged in" without prompting
- **AND** writes to `/root/.config/glab-cli/` from inside the container fail with EROFS

#### Scenario: --gh forwards host env token
#### Scenario: --gh keeps the host token out of the agent container

- **GIVEN** `GH_TOKEN=ghp_x` is exported in the host shell
- **WHEN** user runs `claude-docker --gh ~/repo`
- **THEN** `echo $GH_TOKEN` inside the container prints `ghp_x`
- **THEN** `echo $GH_TOKEN` inside the agent container prints `claude-docker-proxy`
- **AND** `gh api /user` inside the agent container succeeds via the sidecar

#### Scenario: --gh falls back to gh auth token
#### Scenario: --gh falls back to gh auth token for the sidecar

- **GIVEN** neither `GH_TOKEN` nor `GITHUB_TOKEN` is set in the host shell
- **AND** the host has `gh` on PATH and the user is authenticated (`gh auth status` succeeds)
- **WHEN** user runs `claude-docker --gh ~/repo`
- **THEN** `echo $GH_TOKEN` inside the container prints the token returned by `gh auth token`
- **THEN** authenticated GitHub access works inside the agent container
- **AND** the token returned by host `gh auth token` is not present in the agent container's environment

#### Scenario: --gh is silent when gh is unavailable

- **GIVEN** neither `GH_TOKEN` nor `GITHUB_TOKEN` is set in the host shell
- **AND** `gh` is not on the host PATH (or `gh auth token` exits non-zero)
- **WHEN** user runs `claude-docker --gh ~/repo`
- **THEN** the container starts without a `GH_TOKEN` env var and no error is printed
- **THEN** the container starts without a sidecar, without a `GH_TOKEN` env var, and no error is printed

#### Scenario: --gh-direct restores legacy forwarding

- **GIVEN** `GH_TOKEN=ghp_x` is exported in the host shell
- **WHEN** user runs `claude-docker --gh-direct ~/repo`
- **THEN** `echo $GH_TOKEN` inside the agent container prints `ghp_x`
- **AND** no sidecar container is started

#### Scenario: --gh and --gh-direct together are rejected

- **WHEN** user runs `claude-docker --gh --gh-direct ~/repo`
- **THEN** `run.sh` exits non-zero with an error naming the conflicting flags
- **AND** no container or sidecar is started

#### Scenario: --tfe mounts host TFC credentials read-only

Expand All @@ -117,14 +142,23 @@ All credential bind-mounts SHALL be read-only so a compromised container cannot

### Requirement: In-container gh login persists only under --gh

Because macOS `gh` uses the Keychain (no host file to mount), the container SHALL support a fresh `gh auth login` whose resulting `~/.config/gh/` persists across runs via the existing `claude-code-root` volume. Access to that persisted state SHALL be gated on `--gh` being passed in the current run: when `--gh` is not set, `/root/.config/gh/` inside the container MUST appear empty (achieved by overlaying a tmpfs mask) so a prior login cannot grant credentials to a session the user didn't opt in to. The same masking rule SHALL apply to `/root/.config/glab-cli/` when `--glab` is not set, and to `/root/.terraform.d/` when `--tfe` is not set (covering tokens written by an in-container `terraform login` that would otherwise persist via `claude-code-root`).
Because macOS `gh` uses the Keychain (no host file to mount), the container SHALL support a fresh `gh auth login` whose resulting `~/.config/gh/` persists across runs via the existing `claude-code-root` volume. Access to that persisted state SHALL be gated on the current run actually needing it: `/root/.config/gh/` inside the container MUST appear empty (achieved by overlaying a tmpfs mask) unless the run is `--gh` with no host token found (in-container login is the remaining auth path) or `--gh-direct`. In particular, the mask SHALL stay ON when the auth proxy sidecar is active — the placeholder env token makes persisted login state unnecessary, and leaving it accessible would reintroduce a persisted in-container secret. When `--gh` is absent entirely, the mask applies as before. The same masking rule SHALL apply to `/root/.config/glab-cli/` when `--glab` is not set, and to `/root/.terraform.d/` when `--tfe` is not set (covering tokens written by an in-container `terraform login` that would otherwise persist via `claude-code-root`).

#### Scenario: gh login survives container exit under --gh
#### Scenario: gh login survives container exit under --gh without a host token

- **GIVEN** user completes `gh auth login` inside a container launched with `--gh`
- **WHEN** they exit and relaunch with `--gh`
- **GIVEN** the host has no GitHub token (no env vars, `gh auth token` fails)
- **AND** user completes `gh auth login` inside a container launched with `--gh`
- **WHEN** they exit and relaunch with `--gh` (host still has no token)
- **THEN** `gh auth status` reports "logged in" without re-prompting

#### Scenario: persisted gh login is masked while the sidecar is active

- **GIVEN** a prior container run completed `gh auth login` (state persisted in `claude-code-root`)
- **AND** the host has a GitHub token so the sidecar starts
- **WHEN** user runs `claude-docker --gh ~/repo`
- **THEN** `/root/.config/gh/` inside the agent container is empty
- **AND** GitHub access works via the sidecar placeholder token

#### Scenario: prior gh login is hidden without --gh

- **GIVEN** a prior container run completed `gh auth login` (state persisted in `claude-code-root`)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
## MODIFIED Requirements
## REMOVED Requirements

### Requirement: Nested worktrees portable via relative paths

Retracted in favour of the container-only overlay approach below: the host
opt-in precondition (`git config worktree.useRelativePaths true` plus a host
`git worktree repair --relative-paths`) is no longer required, so the old
scenarios describing that host-side opt-in no longer apply.

## ADDED Requirements

### Requirement: Nested worktrees portable via a container-only git config overlay

When a git worktree is nested inside its repository's directory tree (e.g. `<repo>/.claude/worktrees/<name>`), the same worktree directory mounted into the container at a different absolute path SHALL function for `git status`, `git log`, `git diff`, `git commit`, `git worktree add`, and `git worktree list` without requiring `git worktree repair`. This applies in both directions — host-created worktrees work in the container after a one-time `git worktree repair --relative-paths` (only for pre-existing absolute-path worktrees), and container-created worktrees work on the host with no extra step — because the relative offset between the worktree's `.git` link file and the repo's `.git/worktrees/<name>/` directory is preserved by any bind mount that includes the entire repo tree.

For every workspace whose `.git/config` is a regular file (i.e. the main repo, not a worktree pointer), `run.sh` SHALL inject a container-only `.git/config` overlay by copying the host's `.git/config` into the existing `$stage` directory, appending a `[core]` section bumping `repositoryformatversion` to 1 plus `[extensions] relativeWorktrees = true` and `[worktree] useRelativePaths = true`, and bind-mounting that file over `/workspaces/<name>/.git/config` in the container.
Expand Down
9 changes: 6 additions & 3 deletions openspec/specs/cli-help/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,17 @@ Let users discover every `claude-docker` wrapper flag directly from the terminal

The help output SHALL include a one-line description for each of the following, grouped so wrapper flags are visually distinct from the `--` passthrough contract:

- Wrapper flags: `--yolo`, `--ephemeral`, `--ro`, `--aws`, `--gh`, `--glab`, `--iterm`, `--tmux`, `-h`/`--help`.
- Wrapper flags: `--yolo`, `--ephemeral`, `--ro`, `--aws`, `--gh`, `--glab`, `--iterm`, `--tmux`, `--claude-dir`, `-h`/`--help`.
- The `--` separator and its passthrough semantics for `claude` flags.
- Positional workspace arguments and the default-to-`$PWD` behaviour.
- The `CLAUDE_DOCKER_TMUX` environment variable and its accepted values (`1`, `cc`).
- The `CLAUDE_DOCKER_CONFIG_DIR` environment variable and its relationship to `--claude-dir`.
- A brief note that `settings.docker.json` is mounted as `settings.json` in the container.

#### Scenario: All wrapper flags documented

- **WHEN** user runs `claude-docker --help`
- **THEN** the output contains each of `--yolo`, `--ephemeral`, `--ro`, `--aws`, `--gh`, `--glab`, `--iterm`, `--tmux`, `-h`, `--help`, `--`, and `CLAUDE_DOCKER_TMUX`
- **THEN** the output contains each of `--yolo`, `--ephemeral`, `--ro`, `--aws`, `--gh`, `--glab`, `--iterm`, `--tmux`, `--claude-dir`, `-h`, `--help`, `--`, `CLAUDE_DOCKER_TMUX`, `CLAUDE_DOCKER_CONFIG_DIR`, and `settings.docker.json`

#### Scenario: Each wrapper flag has an explanation

Expand Down Expand Up @@ -65,9 +67,10 @@ When help is printed, `run.sh` SHALL NOT default an empty workspace list to `$PW
#### Scenario: No staging dir created

- **WHEN** user runs `claude-docker --help`
- **THEN** no `claude-docker-host.*` directory is left under `$TMPDIR` after the command returns
- **THEN** no `host.*` directory is left under `$HOME/.cache/claude-docker/` after the command returns

#### Scenario: No docker process spawned

- **WHEN** user runs `claude-docker --help` on a host with no `docker` binary on PATH
- **THEN** the command still succeeds with exit 0

Loading
Loading