Skip to content

chore(devcontainer): default Claude to max effort and keep more state - #63

Merged
amiralis1365 merged 1 commit into
mainfrom
feat/devcontainer-baseline
Oct 22, 2026
Merged

amiralis1365 merged 1 commit into
mainfrom
feat/devcontainer-baseline

Conversation

@amiralis1365

@amiralis1365 amiralis1365 commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #62.

Two volumes are renamed. Without the copy in "Before and after your first rebuild", the first rebuild after this merges starts both renamed volumes empty. gh and Claude Code need logging in again, and Claude's /resume sessions are gone from the container. The old volumes stay on the host until you remove them.

Summary

  • Claude Code starts every session at max effort. containerEnv sets CLAUDE_CODE_EFFORT_LEVEL=max, because the settings file's effortLevel accepts only low to xhigh and quietly drops max. The variable outranks /effort, so start a lighter session as CLAUDE_CODE_EFFORT_LEVEL=auto claude.

  • .claude.json survives a rebuild. CLAUDE_CONFIG_DIR=/home/vscode/.claude keeps it in the claude-config volume, beside the credentials and the /resume transcripts. Until now it landed in ~/ and was lost.

  • The volumes take the org's names. musher-catalog-${devcontainerId}-gh-config and -claude-config become musher-${devcontainerId}-gh-config and -claude-config, the form engineering-conventions DEVC-08 requires. ${devcontainerId} already makes each one unique to the container, so the catalog- infix only kept them off that form.

  • A rebuild keeps more. Three new musher-${devcontainerId}-<purpose> volumes:

    • codex-config: Codex's login. Codex itself is new too: "npm:@openai/codex" = "0.143.0" in .devcontainer/mise.toml (npm's package, since Node is already a Feature), and base_verify_tools checks it beside the other pinned CLIs.
    • shell-history: zsh, bash and Node REPL history. base_setup_shell_history points zsh and bash there, and NODE_REPL_HISTORY points the REPL there. There's no PYTHON_HISTORY or PSQL_HISTORY, because the image's python3 is 3.12, which ignores it, and there is no psql.
    • cache: ~/.cache, which XDG_CACHE_HOME and NPM_CONFIG_CACHE already name: npm's, uv's and mise's downloads.

    All three are in base_setup_config_dirs/base_setup_cache_dirs, which take ownership before anything writes.

  • gh logs in by itself, and git pushes through it (.devcontainer/scripts/lib/github-auth.sh):

    • post-create calls github_auth_seed through a new base_setup_github_seed, right after the ownership steps. It stores a token from the host's exported GH_TOKEN (MUSHER_HOST_GH_TOKEN) and writes no git config.
    • A new post-attach.sh calls ensure_github_auth. If gh still has no token, it asks VS Code's forwarded credential helper. Then it clears the helper lines the host's .gitconfig brought for the host's own gh, and runs gh auth setup-git. This waits for attach because VS Code copies the host's ~/.gitconfig only after post-create, and only when none exists: a file written earlier would cost you your name and email.
  • Also: the Claude Code VS Code extension.

github-auth.sh is a verbatim copy of the canonical script in musher-dev/development-container#55, and musher-dev/platform-backstage#114 is the first repository that copies it. Its sibling npm-registry.sh isn't copied, because nothing here installs from GitHub Packages. Type chore: no item, test or published artifact changes.

What a rebuild keeps

Volume (musher-${devcontainerId}-…) Mounted at Keeps
gh-config (renamed) ~/.config/gh The gh login
claude-config (renamed) ~/.claude Claude Code's login, settings and sessions (/resume), and now .claude.json
codex-config (new) ~/.codex Codex's login and settings
shell-history (new) ~/.shell-history zsh, bash and Node REPL history
cache (new) ~/.cache npm's, uv's and mise's downloads

node_modules/ stays where it was, in your checkout on the bind mount. ~/.local (mise's tools, Claude Code) is still not kept; post-create installs it again.

Before and after your first rebuild

Before: copy the two renamed volumes on the host, before you rebuild. <id> is your container's ${devcontainerId}: docker volume ls --format '{{.Name}}' --filter name=musher-catalog- lists the old volumes, and <id> is the part between musher-catalog- and -gh-config. It doesn't change with this branch, because it comes from your folder's path and the config file's path, not from the config's contents.

Old volume New volume Copy, on the host
musher-catalog-<id>-gh-config musher-<id>-gh-config docker run --rm -v musher-catalog-<id>-gh-config:/from -v musher-<id>-gh-config:/to mcr.microsoft.com/devcontainers/base:ubuntu-24.04 cp -a /from/. /to/
musher-catalog-<id>-claude-config musher-<id>-claude-config docker run --rm -v musher-catalog-<id>-claude-config:/from -v musher-<id>-claude-config:/to mcr.microsoft.com/devcontainers/base:ubuntu-24.04 cp -a /from/. /to/

Each command creates the new volume and keeps the files' owner and modes. If you have already rebuilt, quit Claude Code in the container, run them anyway, and start it again. Once you're satisfied, docker volume rm the two old names.

After:

  • .claude.json used to live outside the volume, so Claude Code may run its first-run steps once more. It keeps them from then on.
  • The first attach seeds gh and wires git, unless the copied gh-config already holds your login. If your host's credential lacks the repo and read:org scopes, as some credential managers' tokens do, run gh auth login once. The gh-config volume keeps it.

Test plan

  • devcontainer read-configuration (CLI 0.89.0): parses. The merged mounts are the five musher-${devcontainerId}-… volumes. postAttachCommand, MUSHER_HOST_GH_TOKEN and the three new containerEnv keys are there, and waitFor is unchanged.
  • shellcheck -x --source-path=SCRIPTDIR over all 10 .devcontainer scripts: clean, at CI's -S style too. CI's ShellCheck job and task check:shell both scan .devcontainer/scripts, which holds every one of them.
  • task check (typecheck; 1294 tests, 1280 passed, 14 skipped, 0 failed; shellcheck, actionlint, yamllint, markdownlint) with the pinned tools: clean, as on main. mise install installs every pin, Codex 0.143.0 included.
  • github-auth.sh is byte-identical to the canonical copy at musher-dev/development-container@c12a6ba.
  • The commit passed the repo's pre-commit (shell) and commit-msg hooks.
  • devcontainer build --frozen-lockfile: passes.
  • Fresh-volume devcontainer up in docker-in-docker, with no GH_TOKEN or GITHUB_TOKEN:
    • post-create, post-start and post-attach all ran, and base_verify_tools passed: Claude Code 2.1.307, Codex 0.143.0, gh, task, node, lefthook, actionlint, shellcheck and yamllint. npm ci and the lefthook hooks ran.
    • CLAUDE_CODE_EFFORT_LEVEL=max. Claude Code wrote .claude.json inside ~/.claude, and none exists in ~/.
    • All five mounts and ~/.local are owned by vscode, and nothing under the mounts is owned by anyone else.
    • Each rc file ends with the marker and one HISTFILE line; running the step again adds none. Interactive zsh and bash read HISTFILE from the volume.
    • No ~/.gitconfig after create or attach. post-create and post-attach log the non-fatal "no GitHub credential reached the container yet" line, and a refused MUSHER_HOST_GH_TOKEN is logged and exits 0.
  • The copy command above, on throwaway volumes: it creates the new volume and carries over a .claude.json, a nested session file and a 0600 credentials file with owner 1000:1000, the volume root included. The old volume is left as it was.
  • CI green: validate, and the Dev Container workflow's Feature Lockfile (build --frozen-lockfile), ShellCheck and Env Template Sync jobs. No CI job runs devcontainer up, so the local up above stands in for the lifecycle.
  • After the copy, a rebuild of an existing container: git identity kept, gh green, /resume lists earlier sessions, history survives.

🤖 Generated with Claude Code

Every Claude Code session starts at max effort: CLAUDE_CODE_EFFORT_LEVEL
in containerEnv, since the settings file's effortLevel has no "max".
CLAUDE_CONFIG_DIR keeps .claude.json in the claude-config volume, which
until now lost it, and with it the account and onboarding state, on every
rebuild.

The two volumes drop their catalog- infix for the org's
musher-${devcontainerId}-<purpose> form; ${devcontainerId} already makes
them unique to the container. Three join them: codex-config for Codex,
now a mise pin, plus shell-history and cache, so history and the npm,
uv and mise downloads survive a rebuild too. post-create takes ownership
of each before anything writes to it.

gh logs in by itself, from the host's exported GH_TOKEN or the credential
VS Code forwards, and git's github.com credential points at it. Seeding
runs in post-create but the git wiring runs in a new post-attach hook:
VS Code copies the host's ~/.gitconfig only after post-create and only
when none exists, so wiring git earlier would cost the developer their
identity. lib/github-auth.sh is a verbatim copy of the
development-container template's canonical script.

Also: the Claude Code VS Code extension.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@amiralis1365
amiralis1365 merged commit d50e2f8 into main Oct 22, 2026
4 checks passed
@amiralis1365
amiralis1365 deleted the feat/devcontainer-baseline branch October 22, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dev container: start Claude at max effort, seed gh, add Codex, and keep history and caches across rebuilds

1 participant