From 495ab7bc38e4e5c2d4ae01539ea321db774d0cf7 Mon Sep 17 00:00:00 2001 From: Dick Tump Date: Tue, 25 Aug 2026 13:45:12 +0000 Subject: [PATCH] chore(openspec): archive 4 completed changes, sync main specs add-claude-docker-help, container-git-config-overlay, add-tfe-support, and add-private-registry-support are all fully implemented; validated task-by-task and against live/static code behavior before archiving. Two of the four needed their delta specs refreshed before `openspec archive` would accept them, since main specs had drifted since the deltas were authored: - add-claude-docker-help reused a change-id that had already been archived once in May; its delta was still marked ADDED for requirements that already exist in the main cli-help spec. Changed to MODIFIED. - container-git-config-overlay's delta modeled the new container-only overlay as a MODIFIED requirement, but its scenarios fully replace the old host-opt-in workflow rather than amend it. Split into REMOVED (old behavior) + ADDED (new requirement, distinctly named) so the retraction is explicit instead of silently dropping scenarios. - add-tfe-support's Credentials opt-in / gh-login-persistence deltas were written before gh-auth-proxy-sidecar was archived and no longer matched the current --gh/--gh-direct sidecar behavior; refreshed to layer --tfe onto the current spec text instead of reverting it. add-claude-docker-openspec is intentionally left open: its spec still describes pinning OPENSPEC_VERSION via a Dockerfile ARG, which was superseded by the pins/openspec.env mechanism from automate-version-pins (already implemented on main, not yet archived). automate-version-pins itself is left alone as it's still being worked on. --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/cli-help/spec.md | 2 +- .../tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/package-managers/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../2026-08-25-add-tfe-support}/design.md | 0 .../2026-08-25-add-tfe-support}/proposal.md | 0 .../specs/external-cli-tools/spec.md | 60 +++++++--- .../specs/package-managers/spec.md | 0 .../2026-08-25-add-tfe-support}/tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/multi-workspace-mounts/spec.md | 11 +- .../tasks.md | 0 openspec/specs/cli-help/spec.md | 9 +- openspec/specs/external-cli-tools/spec.md | 67 ++++++++++- openspec/specs/multi-workspace-mounts/spec.md | 70 +++++++----- openspec/specs/package-managers/spec.md | 106 +++++++++++++++++- 25 files changed, 272 insertions(+), 53 deletions(-) rename openspec/changes/{add-claude-docker-help => archive/2026-08-25-add-claude-docker-help}/.openspec.yaml (100%) rename openspec/changes/{add-claude-docker-help => archive/2026-08-25-add-claude-docker-help}/design.md (100%) rename openspec/changes/{add-claude-docker-help => archive/2026-08-25-add-claude-docker-help}/proposal.md (100%) rename openspec/changes/{add-claude-docker-help => archive/2026-08-25-add-claude-docker-help}/specs/cli-help/spec.md (99%) rename openspec/changes/{add-claude-docker-help => archive/2026-08-25-add-claude-docker-help}/tasks.md (100%) rename openspec/changes/{add-private-registry-support => archive/2026-08-25-add-private-registry-support}/.openspec.yaml (100%) rename openspec/changes/{add-private-registry-support => archive/2026-08-25-add-private-registry-support}/design.md (100%) rename openspec/changes/{add-private-registry-support => archive/2026-08-25-add-private-registry-support}/proposal.md (100%) rename openspec/changes/{add-private-registry-support => archive/2026-08-25-add-private-registry-support}/specs/package-managers/spec.md (100%) rename openspec/changes/{add-private-registry-support => archive/2026-08-25-add-private-registry-support}/tasks.md (100%) rename openspec/changes/{add-tfe-support => archive/2026-08-25-add-tfe-support}/.openspec.yaml (100%) rename openspec/changes/{add-tfe-support => archive/2026-08-25-add-tfe-support}/design.md (100%) rename openspec/changes/{add-tfe-support => archive/2026-08-25-add-tfe-support}/proposal.md (100%) rename openspec/changes/{add-tfe-support => archive/2026-08-25-add-tfe-support}/specs/external-cli-tools/spec.md (69%) rename openspec/changes/{add-tfe-support => archive/2026-08-25-add-tfe-support}/specs/package-managers/spec.md (100%) rename openspec/changes/{add-tfe-support => archive/2026-08-25-add-tfe-support}/tasks.md (100%) rename openspec/changes/{container-git-config-overlay => archive/2026-08-25-container-git-config-overlay}/.openspec.yaml (100%) rename openspec/changes/{container-git-config-overlay => archive/2026-08-25-container-git-config-overlay}/design.md (100%) rename openspec/changes/{container-git-config-overlay => archive/2026-08-25-container-git-config-overlay}/proposal.md (100%) rename openspec/changes/{container-git-config-overlay => archive/2026-08-25-container-git-config-overlay}/specs/multi-workspace-mounts/spec.md (91%) rename openspec/changes/{container-git-config-overlay => archive/2026-08-25-container-git-config-overlay}/tasks.md (100%) diff --git a/openspec/changes/add-claude-docker-help/.openspec.yaml b/openspec/changes/archive/2026-08-25-add-claude-docker-help/.openspec.yaml similarity index 100% rename from openspec/changes/add-claude-docker-help/.openspec.yaml rename to openspec/changes/archive/2026-08-25-add-claude-docker-help/.openspec.yaml diff --git a/openspec/changes/add-claude-docker-help/design.md b/openspec/changes/archive/2026-08-25-add-claude-docker-help/design.md similarity index 100% rename from openspec/changes/add-claude-docker-help/design.md rename to openspec/changes/archive/2026-08-25-add-claude-docker-help/design.md diff --git a/openspec/changes/add-claude-docker-help/proposal.md b/openspec/changes/archive/2026-08-25-add-claude-docker-help/proposal.md similarity index 100% rename from openspec/changes/add-claude-docker-help/proposal.md rename to openspec/changes/archive/2026-08-25-add-claude-docker-help/proposal.md diff --git a/openspec/changes/add-claude-docker-help/specs/cli-help/spec.md b/openspec/changes/archive/2026-08-25-add-claude-docker-help/specs/cli-help/spec.md similarity index 99% rename from openspec/changes/add-claude-docker-help/specs/cli-help/spec.md rename to openspec/changes/archive/2026-08-25-add-claude-docker-help/specs/cli-help/spec.md index 6d407b8..f932d4e 100644 --- a/openspec/changes/add-claude-docker-help/specs/cli-help/spec.md +++ b/openspec/changes/archive/2026-08-25-add-claude-docker-help/specs/cli-help/spec.md @@ -1,4 +1,4 @@ -## ADDED Requirements +## MODIFIED Requirements ### Requirement: Help flag prints usage and exits diff --git a/openspec/changes/add-claude-docker-help/tasks.md b/openspec/changes/archive/2026-08-25-add-claude-docker-help/tasks.md similarity index 100% rename from openspec/changes/add-claude-docker-help/tasks.md rename to openspec/changes/archive/2026-08-25-add-claude-docker-help/tasks.md diff --git a/openspec/changes/add-private-registry-support/.openspec.yaml b/openspec/changes/archive/2026-08-25-add-private-registry-support/.openspec.yaml similarity index 100% rename from openspec/changes/add-private-registry-support/.openspec.yaml rename to openspec/changes/archive/2026-08-25-add-private-registry-support/.openspec.yaml diff --git a/openspec/changes/add-private-registry-support/design.md b/openspec/changes/archive/2026-08-25-add-private-registry-support/design.md similarity index 100% rename from openspec/changes/add-private-registry-support/design.md rename to openspec/changes/archive/2026-08-25-add-private-registry-support/design.md diff --git a/openspec/changes/add-private-registry-support/proposal.md b/openspec/changes/archive/2026-08-25-add-private-registry-support/proposal.md similarity index 100% rename from openspec/changes/add-private-registry-support/proposal.md rename to openspec/changes/archive/2026-08-25-add-private-registry-support/proposal.md diff --git a/openspec/changes/add-private-registry-support/specs/package-managers/spec.md b/openspec/changes/archive/2026-08-25-add-private-registry-support/specs/package-managers/spec.md similarity index 100% rename from openspec/changes/add-private-registry-support/specs/package-managers/spec.md rename to openspec/changes/archive/2026-08-25-add-private-registry-support/specs/package-managers/spec.md diff --git a/openspec/changes/add-private-registry-support/tasks.md b/openspec/changes/archive/2026-08-25-add-private-registry-support/tasks.md similarity index 100% rename from openspec/changes/add-private-registry-support/tasks.md rename to openspec/changes/archive/2026-08-25-add-private-registry-support/tasks.md diff --git a/openspec/changes/add-tfe-support/.openspec.yaml b/openspec/changes/archive/2026-08-25-add-tfe-support/.openspec.yaml similarity index 100% rename from openspec/changes/add-tfe-support/.openspec.yaml rename to openspec/changes/archive/2026-08-25-add-tfe-support/.openspec.yaml diff --git a/openspec/changes/add-tfe-support/design.md b/openspec/changes/archive/2026-08-25-add-tfe-support/design.md similarity index 100% rename from openspec/changes/add-tfe-support/design.md rename to openspec/changes/archive/2026-08-25-add-tfe-support/design.md diff --git a/openspec/changes/add-tfe-support/proposal.md b/openspec/changes/archive/2026-08-25-add-tfe-support/proposal.md similarity index 100% rename from openspec/changes/add-tfe-support/proposal.md rename to openspec/changes/archive/2026-08-25-add-tfe-support/proposal.md diff --git a/openspec/changes/add-tfe-support/specs/external-cli-tools/spec.md b/openspec/changes/archive/2026-08-25-add-tfe-support/specs/external-cli-tools/spec.md similarity index 69% rename from openspec/changes/add-tfe-support/specs/external-cli-tools/spec.md rename to openspec/changes/archive/2026-08-25-add-tfe-support/specs/external-cli-tools/spec.md index f607581..5853f2e 100644 --- a/openspec/changes/add-tfe-support/specs/external-cli-tools/spec.md +++ b/openspec/changes/archive/2026-08-25-add-tfe-support/specs/external-cli-tools/spec.md @@ -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_` variables are out of scope for this opt-in. @@ -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 @@ -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`) diff --git a/openspec/changes/add-tfe-support/specs/package-managers/spec.md b/openspec/changes/archive/2026-08-25-add-tfe-support/specs/package-managers/spec.md similarity index 100% rename from openspec/changes/add-tfe-support/specs/package-managers/spec.md rename to openspec/changes/archive/2026-08-25-add-tfe-support/specs/package-managers/spec.md diff --git a/openspec/changes/add-tfe-support/tasks.md b/openspec/changes/archive/2026-08-25-add-tfe-support/tasks.md similarity index 100% rename from openspec/changes/add-tfe-support/tasks.md rename to openspec/changes/archive/2026-08-25-add-tfe-support/tasks.md diff --git a/openspec/changes/container-git-config-overlay/.openspec.yaml b/openspec/changes/archive/2026-08-25-container-git-config-overlay/.openspec.yaml similarity index 100% rename from openspec/changes/container-git-config-overlay/.openspec.yaml rename to openspec/changes/archive/2026-08-25-container-git-config-overlay/.openspec.yaml diff --git a/openspec/changes/container-git-config-overlay/design.md b/openspec/changes/archive/2026-08-25-container-git-config-overlay/design.md similarity index 100% rename from openspec/changes/container-git-config-overlay/design.md rename to openspec/changes/archive/2026-08-25-container-git-config-overlay/design.md diff --git a/openspec/changes/container-git-config-overlay/proposal.md b/openspec/changes/archive/2026-08-25-container-git-config-overlay/proposal.md similarity index 100% rename from openspec/changes/container-git-config-overlay/proposal.md rename to openspec/changes/archive/2026-08-25-container-git-config-overlay/proposal.md diff --git a/openspec/changes/container-git-config-overlay/specs/multi-workspace-mounts/spec.md b/openspec/changes/archive/2026-08-25-container-git-config-overlay/specs/multi-workspace-mounts/spec.md similarity index 91% rename from openspec/changes/container-git-config-overlay/specs/multi-workspace-mounts/spec.md rename to openspec/changes/archive/2026-08-25-container-git-config-overlay/specs/multi-workspace-mounts/spec.md index 89b29b9..a27bf83 100644 --- a/openspec/changes/container-git-config-overlay/specs/multi-workspace-mounts/spec.md +++ b/openspec/changes/archive/2026-08-25-container-git-config-overlay/specs/multi-workspace-mounts/spec.md @@ -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. `/.claude/worktrees/`), 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//` 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//.git/config` in the container. diff --git a/openspec/changes/container-git-config-overlay/tasks.md b/openspec/changes/archive/2026-08-25-container-git-config-overlay/tasks.md similarity index 100% rename from openspec/changes/container-git-config-overlay/tasks.md rename to openspec/changes/archive/2026-08-25-container-git-config-overlay/tasks.md diff --git a/openspec/specs/cli-help/spec.md b/openspec/specs/cli-help/spec.md index 9a9a01b..9a0ca44 100644 --- a/openspec/specs/cli-help/spec.md +++ b/openspec/specs/cli-help/spec.md @@ -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 @@ -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 + diff --git a/openspec/specs/external-cli-tools/spec.md b/openspec/specs/external-cli-tools/spec.md index 11f63f1..618f0c6 100644 --- a/openspec/specs/external-cli-tools/spec.md +++ b/openspec/specs/external-cli-tools/spec.md @@ -3,7 +3,9 @@ ## Purpose Provide `gh`, `glab`, and AWS CLI v2 inside the container with minimal re-auth friction, using host credential passthrough where the tool's macOS storage is file-based and in-container persistence otherwise. + ## Requirements + ### Requirement: gh, glab, aws v2 installed The container image SHALL ship with `gh`, `glab`, and `aws` (v2) on the default PATH, built arch-aware for both `amd64` and `arm64`. @@ -39,17 +41,20 @@ Host credentials (files or env vars) SHALL NOT reach the container unless the us 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_` variables are out of scope for this opt-in. All credential bind-mounts SHALL be read-only so a compromised container cannot rewrite host config or tokens. `~/.aws/credentials` and `~/.aws/cli/cache/` SHALL NEVER be mounted, even under `--aws`. #### Scenario: No flags means no credentials -- **GIVEN** host has `~/.aws/config`, `~/.config/glab-cli/config.yml`, and `GH_TOKEN=ghp_x` exported +- **GIVEN** host has `~/.aws/config`, `~/.config/glab-cli/config.yml`, `~/.terraform.d/credentials.tfrc.json`, and `GH_TOKEN=ghp_x` and `TF_TOKEN_app_terraform_io=tfc_x` exported - **AND** a prior container run completed `gh auth login` (state persisted in `claude-code-root`) - **WHEN** user runs `claude-docker ~/repo` - **THEN** `/root/.aws/` does not exist inside the container - **AND** `/root/.config/glab-cli/` is empty inside the container +- **AND** `/root/.terraform.d/` is empty inside the container - **AND** `echo $GH_TOKEN` inside the container is empty +- **AND** `echo $TF_TOKEN_app_terraform_io` inside the container is empty - **AND** `gh auth status` inside the container reports "not logged in" #### Scenario: --aws grants scoped AWS access @@ -102,9 +107,30 @@ All credential bind-mounts SHALL be read-only so a compromised container cannot - **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 + +- **GIVEN** the host has a valid `~/.terraform.d/credentials.tfrc.json` with an `app.terraform.io` token entry +- **WHEN** user runs `claude-docker --tfe ~/repo` +- **THEN** `/root/.terraform.d/credentials.tfrc.json` inside the container contains the host file's contents +- **AND** writes to `/root/.terraform.d/credentials.tfrc.json` from inside the container fail with EROFS + +#### Scenario: --tfe forwards host TF_TOKEN_app_terraform_io + +- **GIVEN** `TF_TOKEN_app_terraform_io=tfc_xyz` is exported in the host shell +- **WHEN** user runs `claude-docker --tfe ~/repo` +- **THEN** `echo $TF_TOKEN_app_terraform_io` inside the container prints `tfc_xyz` + +#### Scenario: --tfe is silent when neither file nor env var is set + +- **GIVEN** the host has no `~/.terraform.d/credentials.tfrc.json` and no `TF_TOKEN_app_terraform_io` exported +- **WHEN** user runs `claude-docker --tfe ~/repo` +- **THEN** the container starts without error +- **AND** `/root/.terraform.d/` inside the container is empty +- **AND** `echo $TF_TOKEN_app_terraform_io` inside the container is empty + ### 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 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. +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 without a host token @@ -135,6 +161,13 @@ Because macOS `gh` uses the Keychain (no host file to mount), the container SHAL - **THEN** `glab auth status` inside the container reports no authenticated host - **AND** `/root/.config/glab-cli/` inside the container is empty +#### Scenario: prior terraform login is hidden without --tfe + +- **GIVEN** a prior container run completed `terraform login app.terraform.io` (the resulting credentials file persists under `claude-code-root` in `/root/.terraform.d/`) +- **WHEN** user runs `claude-docker ~/repo` without `--tfe` +- **THEN** `/root/.terraform.d/` inside the container is empty +- **AND** no `credentials.tfrc.json` from the prior session is readable inside the container + ### Requirement: git-lfs installed and LFS filters registered The container image SHALL ship with `git-lfs` on the default PATH so that git @@ -167,3 +200,33 @@ installed unpinned from the distribution archive, consistent with the existing - **WHEN** git inside the container checks out an LFS-tracked file - **THEN** the system-registered LFS filter is invoked rather than the file being passed through as an unsmudged pointer +### Requirement: tfenv installed and version-pinned + +The container image SHALL ship with `tfenv` on the default PATH so users can fetch a project-pinned `terraform` binary on demand. The `tfenv` install SHALL pin the upstream version via a Dockerfile `ARG` and verify the downloaded artifact against an `ARG`-pinned sha256 before installation. The pinned hash SHALL live in version control, not be fetched from the source URL at build time. The image SHALL NOT pre-install any `terraform` binary version; version selection is the project's responsibility, exercised at runtime via `tfenv install` (typically driven by a `.terraform-version` file in the workspace). The `terraform` dispatcher shim that tfenv ships (a bash script, not a terraform binary) MAY be on PATH so that `terraform ` works after `tfenv install` without further PATH manipulation. + +#### Scenario: tfenv present on PATH, no terraform binary version installed + +- **WHEN** the container launches +- **THEN** `tfenv --version` succeeds +- **AND** running `terraform version` before any `tfenv install` exits non-zero (the dispatcher reports no version available, and no real terraform binary exists under tfenv's versions directory) + +#### Scenario: build fails on tampered tfenv archive + +- **GIVEN** a build where the tfenv source archive does not match the pinned `TFENV_SHA256` ARG +- **WHEN** the Dockerfile runs `sha256sum -c` +- **THEN** the build fails with a non-zero exit code before installation +- **AND** no `tfenv` binary is installed onto the default PATH + +#### Scenario: version bumps require sha256 bumps in the same commit + +- **WHEN** a contributor changes `TFENV_VERSION` without updating `TFENV_SHA256` +- **THEN** the next build fails sha256 verification +- **AND** the failure surfaces in CI before merge + +#### Scenario: tfenv install fetches a project-pinned terraform at runtime + +- **GIVEN** a workspace containing a `.terraform-version` file with the contents `1.9.5` +- **WHEN** the user runs `tfenv install` inside the container +- **THEN** tfenv downloads terraform 1.9.5 from `releases.hashicorp.com` and installs it +- **AND** subsequent `terraform version` invocations report `1.9.5` + diff --git a/openspec/specs/multi-workspace-mounts/spec.md b/openspec/specs/multi-workspace-mounts/spec.md index 4df960b..c565699 100644 --- a/openspec/specs/multi-workspace-mounts/spec.md +++ b/openspec/specs/multi-workspace-mounts/spec.md @@ -95,35 +95,6 @@ For every workspace argument beyond the first, `run.sh` SHALL pass `--add-dir /.claude/worktrees/`) AND the repo has been opted in to relative-path worktrees (`git config worktree.useRelativePaths true` plus `git worktree repair --relative-paths` for any pre-existing worktree), 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, and container-created worktrees work on the host — because the relative offset between the worktree's `.git` link file and the repo's `.git/worktrees//` directory is preserved by any bind mount that includes the entire repo tree. - -The container image SHALL ship a `git` version (≥ 2.48) that supports both reading and writing relative-path worktrees, including the `extensions.relativeWorktrees` repository extension that git 2.48+ sets as a safety lock when relative paths are in use. Older git versions refuse to operate on a repo with this extension set, so the container's git MUST be at the supporting version for the workflow to function at all once the host has opted in. - -This requirement assumes the user's host git is also ≥ 2.48 (needed to write the initial `--relative-paths` repair). Hosts on older git fall back to the existing repair-based workflow — see "Sibling worktrees supported". - -#### Scenario: Host-created nested worktree round-trips between host and container without repair - -- **GIVEN** the host has git ≥ 2.48 and ran `git config worktree.useRelativePaths true` in the repo -- **AND** a worktree exists at `/.claude/worktrees/feature-x` (created with relative paths, or migrated via `git worktree repair --relative-paths`) -- **WHEN** the user runs `claude-docker ` on the host and the repo is mounted at `/workspaces/` in the container -- **THEN** `git status` inside `/workspaces//.claude/worktrees/feature-x` succeeds without prompting for repair -- **AND** the user can exit the container and run `git status` in the same worktree on the host without any repair step - -#### Scenario: Container-created worktree is portable to the host - -- **GIVEN** the user is inside `claude-docker` with the repo configured for relative-path worktrees -- **WHEN** they run `git worktree add .claude/worktrees/feature-y -b feature-y` -- **THEN** the link files written under `/.git/worktrees/feature-y/` and `/.claude/worktrees/feature-y/.git` SHALL contain relative paths -- **AND** exiting the container and running `git status` in that worktree from the host succeeds without `git worktree repair` - -#### Scenario: Container's git accepts the extensions.relativeWorktrees flag - -- **GIVEN** a repo on which the host has run `git worktree repair --relative-paths` (which sets `extensions.relativeWorktrees = true` in `.git/config`) -- **WHEN** the user runs `claude-docker ` and runs any git command inside the mounted repo -- **THEN** the command succeeds (i.e. the container's git does NOT abort with `fatal: unknown repository extension found: relativeworktrees`) - ### Requirement: Sibling worktrees supported Users SHALL be able to pass both a repo and its sibling git worktree (or a shared parent) as separate workspace arguments so that git operations across them succeed. Because each workspace argument is bind-mounted at `/workspaces/` in the container, the relative offset between the worktree's `.git` link file and the repo's `.git/worktrees//` directory is NOT preserved (the host parent directory does not appear in the container). For this layout, users MAY need to run `git worktree repair` once inside the container to rewrite the link-file paths, regardless of whether `worktree.useRelativePaths` is set on the host. @@ -181,3 +152,44 @@ This requirement covers only sibling-flattened layouts, and also covers hosts wh - **WHEN** user runs `claude-docker --yolo ~/repo -- --resume` - **THEN** the container launches `claude --dangerously-skip-permissions --resume` + +### 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. `/.claude/worktrees/`), 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//` 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//.git/config` in the container. + +The host's on-disk `.git/config` SHALL NOT be modified by `run.sh` or by any operation performed inside the container. This is required to keep host tools that link against an older libgit2 (notably `gitstatusd`, which powers the Powerlevel10k git prompt) able to open the repo — those tools refuse to open a v1 repo declaring an unknown extension. + +The overlay mount SHALL be writable (not `:ro`), so container-side operations that write to `.git/config` (e.g. `git remote add`, `git branch --set-upstream-to`) succeed. Such writes land in the ephemeral stage copy and are discarded at session end; persistent local-config edits are expected to happen on the host. + +The overlay SHALL NOT be created for workspaces where `.git` is a pointer file rather than a directory (worktrees, submodules). Worktrees mounted alongside their main repo resolve through the main repo's overlay; worktrees mounted standalone (without their main repo) fall back to the existing `git worktree repair` workflow. + +#### Scenario: Container-created nested worktree is portable to the host + +- **GIVEN** the user runs `claude-docker ` and `/.git/config` is a regular file +- **WHEN** they run `git worktree add .claude/worktrees/feature-y -b feature-y` inside the container +- **THEN** `/.git/worktrees/feature-y/gitdir` and `/.claude/worktrees/feature-y/.git` SHALL contain relative paths +- **AND** exiting the container and running `git status` in that worktree from the host SHALL succeed without `git worktree repair` + +#### Scenario: Host's on-disk `.git/config` is not modified by container-side git operations + +- **GIVEN** a repo whose host-visible `.git/config` does NOT contain `extensions.relativeWorktrees` +- **WHEN** the user runs `claude-docker ` and performs any sequence of `git` operations inside (including `git worktree add`, `git remote add`, `git config --local`) +- **THEN** inspecting `/.git/config` from the host after the container exits SHALL show no new `extensions.relativeWorktrees`, no bumped `repositoryformatversion`, and no other writes performed by the container +- **AND** host tools that link against libgit2 versions predating January 2025 (e.g. `gitstatusd`) SHALL continue to open the repo without an "unknown extension" error + +#### Scenario: Container-side `git` sees the relative-paths configuration + +- **GIVEN** the user is inside `claude-docker` with the main repo mounted +- **WHEN** they run `git config --get extensions.relativeWorktrees` and `git config --get worktree.useRelativePaths` +- **THEN** both return `true` +- **AND** `git config --get core.repositoryformatversion` returns `1` + +#### Scenario: Pre-existing absolute-path worktree fixed by one in-container repair + +- **GIVEN** the user has a worktree at `/.claude/worktrees/old` created before this change (absolute paths in its link files) +- **WHEN** they run `claude-docker ` and execute `git worktree repair --relative-paths .claude/worktrees/old` inside +- **THEN** the link files SHALL be rewritten with relative paths +- **AND** subsequent host-side and container-side git operations on that worktree SHALL succeed without further repair + diff --git a/openspec/specs/package-managers/spec.md b/openspec/specs/package-managers/spec.md index f2ac98d..ac2cfb4 100644 --- a/openspec/specs/package-managers/spec.md +++ b/openspec/specs/package-managers/spec.md @@ -81,15 +81,113 @@ Any package installed via `npm install -g` in the image SHALL be installed with ### Requirement: runtime code-fetch capability documented in threat model -The container's threat model documentation SHALL explicitly note that `npx`, `pnpm dlx`, and `uvx` can fetch and execute arbitrary code from public registries (npm and PyPI) at runtime, and that under `--yolo` a prompt-injected workspace can trigger these. The documentation SHALL distinguish `uvx` (new PyPI execution capability) from `pnpm dlx` (functionally equivalent to the already-available `npx`, zero marginal blast radius). +The container's threat model documentation SHALL explicitly note that `npx`, `pnpm dlx`, `uvx`, and `tfenv install` can fetch and execute arbitrary code from public sources at runtime — npm and PyPI for the package managers, `releases.hashicorp.com` for `tfenv install` — and that under `--yolo` a prompt-injected workspace can trigger these. The documentation SHALL distinguish `uvx` (PyPI execution) and `tfenv install` (HashiCorp release-channel execution of an unpinned terraform binary, version-selected by the workspace) from `pnpm dlx` (functionally equivalent to the already-available `npx`). #### Scenario: README threat model includes runtime-fetch bullet - **WHEN** a reader inspects `claude-docker/README.md` § Threat model -- **THEN** the section contains a bullet covering `npx`, `pnpm dlx`, and `uvx` as runtime code-fetch primitives -- **AND** the bullet identifies `uvx` as a new PyPI execution capability not previously available in the image +- **THEN** the section contains a bullet covering `npx`, `pnpm dlx`, `uvx`, and `tfenv install` as runtime code-fetch primitives +- **AND** the bullet identifies `uvx` (PyPI) and `tfenv install` (HashiCorp releases) as runtime-fetch primitives whose downloaded binaries are not pinned in the image #### Scenario: bundled CLIs list includes new tools - **WHEN** a reader inspects the top of `claude-docker/README.md` -- **THEN** the "Bundled CLIs on the default PATH" line lists `uv`, `uvx`, `pnpm`, and `pnpx` alongside the existing entries +- **THEN** the "Bundled CLIs on the default PATH" line lists `uv`, `uvx`, `pnpm`, `pnpx`, and `tfenv` alongside the existing entries + +### Requirement: Private registry passthrough via --registry + +`run.sh` SHALL provide a `--registry` opt-in flag that surfaces the host's +native `uv`, `npm`/`pnpm`, and pip-based (`pip`/`pipenv`) private-registry +configuration into the container using the package managers' own discovery +mechanisms, rather than any wrapper-specific configuration. When `--registry` +is NOT passed, no registry configuration and no registry credentials SHALL +reach the container, and the package managers SHALL fall back to their built-in +public defaults (the npm registry and PyPI). The flag SHALL compose with all +other flags and SHALL NOT require `--aws`. + +This requirement governs registry *configuration* only. The image SHALL NOT be +required to ship `pip`, `pipenv`, or a Python runtime for this requirement to be +met; the forwarded pip configuration applies to whatever pip executes inside the +container (e.g. via `uvx pipenv` or a child image). + +When `--registry` is set, `run.sh`: + +- SHALL mount read-only, and only when present on the host (a missing file is a + silent no-op), each of: the host npm config (`~/.npmrc`, or the file named by + `npm_config_userconfig` / `NPM_CONFIG_USERCONFIG` when set) at `/root/.npmrc`, + `~/.config/uv/uv.toml` at `/root/.config/uv/uv.toml`, and the + platform-appropriate pip config — `~/.config/pip/pip.conf` on Linux or + `~/Library/Application Support/pip/pip.conf` on macOS — at + `/root/.config/pip/pip.conf`. +- SHALL NOT mount `~/.netrc`. Because netrc is a machine-keyed store that + commonly holds credentials for hosts unrelated to the package registry, + forwarding the whole file into a full-egress container is too broad; netrc- + based registry auth is intentionally not supported by this flag (registry + auth belongs in npmrc/pip.conf, the index URL, or `UV_INDEX_*_PASSWORD`). +- SHALL forward, only when set on the host, the native env vars + `npm_config_registry`, `NPM_CONFIG_REGISTRY`, `NODE_AUTH_TOKEN`, `NPM_TOKEN`, + `UV_INDEX_URL`, `UV_DEFAULT_INDEX`, `UV_EXTRA_INDEX_URL`, `UV_INDEX`, + `UV_KEYRING_PROVIDER`, `PIP_INDEX_URL`, `PIP_EXTRA_INDEX_URL`, + `PIP_TRUSTED_HOST`, and `PIPENV_PYPI_MIRROR`. It SHALL NOT forward `UV_NETRC` + (it points uv at a netrc file that is no longer mounted). +- SHALL additionally forward every set host environment variable whose name + matches `UV_INDEX_*_USERNAME` or `UV_INDEX_*_PASSWORD`, so uv's per-index + credential variables (whose names derive from a user-chosen index name) reach + the container without being individually enumerated, while other `UV_*` + variables are NOT blanket-forwarded. +- SHALL append `registry` to the statusline opt-in tag list. + +All mounted config files SHALL be read-only, so the container cannot mutate host +registry credentials and a `--registry` session cannot persist registry +credential state into the `claude-code-root` volume. + +Resolution policy SHALL be whatever the host configuration expresses: the +wrapper imposes none of its own. Setting a default registry/index in the host +config natively replaces the public default (confining resolution to the +configured feed), and re-admitting public registries is expressed in the host's +own native config. + +#### Scenario: no flag means no registry config reaches the container + +- **GIVEN** the host has a `~/.npmrc` with a private `registry=` line and exports `UV_DEFAULT_INDEX` +- **WHEN** the user runs `claude-docker ~/repo` without `--registry` +- **THEN** `/root/.npmrc` inside the container does not contain the host's private registry config +- **AND** `echo $UV_DEFAULT_INDEX` inside the container is empty +- **AND** `pnpm config get registry` returns the public npm default + +#### Scenario: --registry mounts host npmrc read-only + +- **GIVEN** the host has a `~/.npmrc` configuring a private registry with an auth token +- **WHEN** the user runs `claude-docker --registry ~/repo` +- **THEN** `/root/.npmrc` inside the container contains the host file's contents +- **AND** `pnpm config get registry` returns the private registry URL +- **AND** a write to `/root/.npmrc` from inside the container fails with EROFS + +#### Scenario: --registry mounts host pip config and forwards PIP_* env + +- **GIVEN** the host has a pip config (at its platform-appropriate user location) setting a private `index-url`, and exports `PIP_INDEX_URL` +- **WHEN** the user runs `claude-docker --registry ~/repo` +- **THEN** `/root/.config/pip/pip.conf` inside the container contains the host pip config +- **AND** `echo $PIP_INDEX_URL` inside the container prints the host value +- **AND** a pip-based install (e.g. via `uvx pipenv`) resolves against the private index +- **AND** without `--registry`, `/root/.config/pip/pip.conf` is absent and `echo $PIP_INDEX_URL` is empty + +#### Scenario: --registry forwards uv index env vars including dynamic credential vars + +- **GIVEN** the host exports `UV_DEFAULT_INDEX=https://example.test/simple/`, `UV_INDEX_INTERNAL_USERNAME=aws`, and `UV_INDEX_INTERNAL_PASSWORD=tok` +- **WHEN** the user runs `claude-docker --registry ~/repo` +- **THEN** all three variables are present in the container environment +- **AND** other unrelated `UV_*` host variables (e.g. `UV_CACHE_DIR`) are not forwarded + +#### Scenario: --registry is silent when no host registry config exists + +- **GIVEN** the host has no `~/.npmrc`, no `~/.netrc`, no `~/.config/uv/uv.toml`, and none of the forwarded env vars set +- **WHEN** the user runs `claude-docker --registry ~/repo` +- **THEN** the container starts without error +- **AND** the package managers fall back to their public defaults + +#### Scenario: statusline tag reflects the opt-in + +- **WHEN** the user runs `claude-docker --registry ~/repo` +- **THEN** the statusline `docker:` prefix includes `registry` in the opt-in tag list +