Skip to content

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

Open
amiralis1365 wants to merge 2 commits into
mainfrom
feat/devcontainer-baseline
Open

amiralis1365 wants to merge 2 commits into
mainfrom
feat/devcontainer-baseline

Conversation

@amiralis1365

@amiralis1365 amiralis1365 commented Oct 22, 2026 •

Copy link
Copy Markdown

Closes #107.

Summary

  • The dev container builds again. A separate commit pins the base image to mcr.microsoft.com/devcontainers/base:ubuntu-24.04, the template's image. base:ubuntu floats, and it now resolves to Ubuntu 26.04 "resolute", where the docker-in-docker Feature (2.17.0) refuses its default moby option: the build stopped in that Feature, on main too, before any lifecycle script ran.

  • 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.

  • A rebuild keeps more. Two new musher-${devcontainerId}-<purpose> volumes join the gh, Claude and Codex ones:

    • shell-history: zsh, bash, Node, Python and psql history. base_setup_shell_history points zsh and bash there, and containerEnv points the REPLs there.
    • cache: ~/.cache, where containerEnv already sends Go's module and build caches, uv, pip, npm, bun, Deno, ruff and mypy. post-create's task setup no longer starts from an empty Go module and build cache on each rebuild.

    base_setup_config_dirs and base_setup_cache_dirs take ownership of both before anything writes to them.

  • 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 an attach because VS Code copies the host's ~/.gitconfig only after post-create, and only into a container that has none: a file written earlier would cost you your name and email.
  • Claude Code comes from its native installer, which updates itself, instead of the claude-code Feature's npm install. base_install_claude_code is typescript-sdk's, with one fix: the pipeline runs under pipefail. Without it, a failed download hands bash an empty script, bash exits 0, and the three retries never happen. A failed install still fails post-create, as a failed Feature failed the build, and base_verify_tools still checks claude.

  • shfmt leaves the shared script alone. task check:shell runs shfmt -d -i 2 -ci -sr over every *.sh, and those flags would reformat github-auth.sh, which must stay byte-identical to the canonical copy. An .editorconfig section marks it ignore = true, and the three shfmt calls in Taskfile.yml (check:shell, check:shell:files, which the pre-commit hook runs, and fmt) pass --apply-ignore. ShellCheck still scans it.

  • Also: the Claude Code VS Code extension. The commented-out dind-data mount gives way to a note: the docker-in-docker Feature already keeps /var/lib/docker, where the compose services' data lives, on a volume of its own.

github-auth.sh is a verbatim copy of the canonical script in musher-dev/development-container#55; musher-dev/platform-backstage#114 was the first repository to copy it. Its sibling npm-registry.sh isn't copied, because nothing here installs from GitHub Packages. Type chore: the CLI doesn't change, so release-please cuts no release.

What a rebuild keeps

Volume (musher-${devcontainerId}-…) Mounted at Keeps
gh-config ~/.config/gh The gh login
claude-config ~/.claude Claude Code's login, settings and sessions (/resume), and now .claude.json
codex-config ~/.codex Codex's login and settings
shell-history (new) ~/.shell-history zsh, bash, Node, Python and psql history
cache (new) ~/.cache Go's module and build caches, the uv, pip, npm, bun and Deno download caches, and ruff's and mypy's

The compose stack's data, Postgres included, was already kept by the docker-in-docker Feature's own dind-var-lib-docker-${devcontainerId} volume. The git hooks stay where they were, in your checkout on the bind mount. The Go tools task setup installs into /go/bin, Claude Code under ~/.local, and Codex are still not kept; post-create installs them again.

Before and after your first rebuild

  • With the pin, a rebuild works again, on Ubuntu 24.04.
  • No volume is renamed, so there is nothing to copy. The two new volumes start empty, so this rebuild is the last one that loses your shell history and the caches.
  • .claude.json used to live outside the volume, so Claude Code may run its first-run steps once more after this rebuild. It keeps them from then on.
  • Claude Code moves from the Feature's global npm package to the native install, ~/.local/bin/claude. Your login, settings and sessions stay in ~/.claude.
  • The first attach seeds gh and wires git. 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 plus the docker-in-docker Feature's dind-var-lib-docker-${devcontainerId}, and no claude-code Feature remains. postAttachCommand, MUSHER_HOST_GH_TOKEN and the five new containerEnv keys are there, and waitFor is unchanged.
  • shellcheck -x --source-path=SCRIPTDIR over all 8 .devcontainer shell files: clean.
  • The pin: a plain devcontainer build of a clean main checkout (1974505) stops in the docker-in-docker Feature with "(!) The 'moby' option is not supported on ubuntu 'resolute' because 'moby-cli' and related system packages are not available in that distribution." So did this branch before the pin commit.
  • On the host, with the Taskfile's pinned shfmt 3.13.0, ShellCheck 0.11.0 and actionlint 1.7.11: task check:shell, task check:repo-policy and task check:workflow are clean, as on main. Without the exemption, shfmt -d -i 2 -ci -sr rewrites github-auth.sh; with --apply-ignore it skips that one file and still reports the others.
  • github-auth.sh is byte-identical to the canonical copy at musher-dev/development-container@c12a6ba. post-attach.sh is the template's, with this repository's root-relative # shellcheck source= path.
  • The installer's retries, against an unreachable URL: typescript-sdk's form returns 0 after one failed download; this one tries three times, then returns 1.
  • devcontainer build and up with fresh volumes and no GH_TOKEN (CLI 0.89.0), on this branch's config. The run used a copy pinned to base:ubuntu-24.04 before the pin commit existed, identical to the committed file apart from the comment above the image line:
    • post-create logged the ownership step for ~/.shell-history and the cache dirs, the non-fatal "no GitHub credential reached the container yet" line and both history lines. It then installed Claude Code 2.1.307 natively (no npm package left), passed base_verify_tools, and ran task setup, which installed the hooks. postStart brought Postgres up healthy, and post-attach ran and logged the same non-fatal line.
    • CLAUDE_CODE_EFFORT_LEVEL is max. Every mount, and ~/.config, ~/.local and ~/.local/bin, is owned by vscode. Each rc file has one marker and one line, and interactive zsh and bash read HISTFILE from ~/.shell-history. ensure_github_auth resolves, and there is no ~/.gitconfig.
    • Cleanup removed the container and its six volumes.
    • This run predates the pipefail line, which changes only what a failed download does; the retry test above covers that.
  • CI's Lint job inside that container (task check:ci:lint): on this branch, every step up to check:lint passes, check:shell included (the image has no rg, so check:repo-policy checks nothing there; the host run above covers it). check:lint then fails on main exactly as here, because the Go Feature installs the newest Go, 1.27.3, which golangci-lint 2.11.3 cannot typecheck against. CI takes Go from go.mod (1.26.5).
  • The commit passed the repo's pre-commit hook (shell: shfmt and ShellCheck on the four staged scripts) and its commit-msg hook.
  • The push passed the repo's pre-push hook (task check:pre-push): the policy test, golangci-lint 2.11.3 with no issues, and go test -v -race ./... with every package passing, on Go 1.26.5, the go.mod version.
  • CI green. Lint, Build and NilAway pass. Test fails in its first step, check:vuln, on 20 called vulnerabilities that main has too: Go 1.26.5's standard library (fixed in 1.26.6, 1.26.9 and 1.26.10) and the grpc and otel exporter versions in go.sum. 19 of the 20 advisories were published after main's last green run on 2026-08-05, and the 20th, GO-2026-5026, gained its standard-library range on 2026-10-16 (OSV). This PR changes no Go file, go.mod or go.sum. The two Smoke jobs need Test, so they were skipped. The tests Test runs after check:vuln passed in the pre-push hook above. No job builds the dev container.
  • A rebuild of an existing container: git identity kept, gh green, /resume lists earlier sessions, history survives.

🤖 Generated with Claude Code

amiralis1365 and others added 2 commits October 22, 2026 00:12
Claude Code's settings file has no "max" effort, so CLAUDE_CODE_EFFORT_LEVEL
is the only way to start every session there. It outranks /effort, and
`CLAUDE_CODE_EFFORT_LEVEL=auto claude` opts one session out.

Without CLAUDE_CONFIG_DIR, Claude writes .claude.json to ~/, outside its
volume, so every rebuild lost the account and onboarding state. Two new
volumes keep shell and REPL history and the download caches, Go's modules
and builds among them, across a rebuild as well; post-create takes
ownership of both before anything writes to them.

post-create now seeds gh from the host's exported GH_TOKEN, and a new
post-attach hook wires git to it. Not earlier: VS Code copies the host's
~/.gitconfig only after post-create, and only when none exists, so a file
written first would cost the developer their identity. github-auth.sh is a
verbatim copy of the canonical file in musher-dev/development-container.
shfmt leaves that copy as it is (.editorconfig, `--apply-ignore`), because
its formatting is the canonical file's; ShellCheck still scans it.

Claude Code comes from its native installer, as in the SDK repositories,
instead of the claude-code Feature's npm install. The pipeline runs under
pipefail, so a failed download is retried: without it bash runs the empty
script, exits 0, and retry never sees the failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
base:ubuntu now resolves to Ubuntu 26.04 "resolute", where the
docker-in-docker Feature refuses its default moby option, so the dev
container no longer builds, on main as well. ubuntu-24.04 is the
template's image and the release these containers ran before the tag
moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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: fix the build, start Claude at max effort, seed gh, and keep history and caches across rebuilds

1 participant