Skip to content

fix(devcontainer): pin the image's tools and own the credential directory - #51

Merged
protonspy merged 3 commits into
mainfrom
fix/devcontainer-pins-and-home
Sep 16, 2026
Merged

protonspy merged 3 commits into
mainfrom
fix/devcontainer-pins-and-home

Conversation

@protonspy

Copy link
Copy Markdown
Owner

What changed

Two failures measured in one dev-container session, both of them Docker's defaults rather than anything scc decided.

An unversioned npm install -g is a layer whose hash never changes. Docker serves the first build's answer for as long as the cache survives while the registry and this workspace both move on — and the rebuild that looks like it fixed that did not. Measured: an image rebuilt the same day carried scc v0.23.0 into a workspace whose settings.json registers the UserPromptSubmit hook v0.24.0 added, so every prompt of the session answered unknown hook stage "user-prompt". The three tools are pinned to build ARGs now; bumping an ARG is what invalidates the layer. SCC_VERSION is the one of the three with a contract to the workspace it runs in — scc scaffolds the harness hooks and this scc runs them — so the comment says to keep it at or above the version that scaffolded the workspace. Claude Code updates itself at runtime and codegraph answers to nobody here; those two are pinned only so a rebuild is reproducible.

A named volume mounted onto a path the image never created is created root-owned. Docker copies an image path's content and its ownership into a fresh named volume only when that path already exists. Without /home/node/.claude in the image, the agent's first write of every session was EACCES: permission denied, mkdir '/home/node/.claude/session-env', and the login it could not store then read back as "not logged in". The image creates and chowns it.

Both hunks land identically in the seeded .devcontainer/Dockerfile and in the template it came from, and assets.Version goes 32 → 33 with the fingerprint updated.

Two commits ride along, separated so approving the fix is not approving them:

  • chore(workspace) — the CodeGraph and RTK usage blocks scc launch splices into the entry file, which this checkout had been carrying uncommitted.
  • docs(skills) — gauntlet-loop narrowed from "any goal, with a named artifact as the bar" to scc's own methodology, where the bar is an arm: two harness configurations on the same ticket, critic blind.

Which spec or plan

Neither — a fix. No box to tick.

How it was verified

Gate Result
go build ./... pass
golangci-lint fmt --diff pass
golangci-lint run 0 issues.
go test ./... pass, whole suite
scc validate 10 validators · 0 findings

Two new tests in internal/cli/init_test.go hold each half against the seeded files rather than against the prose that explains them: TestTheSeededImagePinsTheToolItMustMatch asserts the ARG and the @${SCC_VERSION} install, and TestTheSeededImageOwnsTheCredentialDirectory asserts the mkdir/chown and that devcontainer.json still mounts a volume there, since neither file states the failure alone.

Reviews. scc-code-review: changes-requested — one major, "the working tree mixes unrelated changes", which is what the three-commit split above answers; its minor (there is no build.args override path in devcontainer.json, so SCC_VERSION is bumped by editing the Dockerfile) is accepted as-is and matches how the seed is meant to be owned. scc-security-review: clean — no reachable findings. It notes the ARGs interpolate unquoted into the su node -c string, and that this crosses no trust boundary: whoever passes --build-arg already controls the build host, and the same pattern already sits untouched in this file for GO_VERSION and GOLANGCI_VERSION.

Known, deliberately not done here

SCC_VERSION stays at 0.25.0 rather than moving to the version this template ships in. Template 33 changes nothing but the Dockerfile — no new hook stage, no new vocabulary — so 0.25.0 is a binary that understands everything this scaffold writes, and pinning to a version that is not on npm yet would leave the image unbuildable in the window before the release publishes.

…tory

`npm install -g <pkg>` with no version is a layer whose hash never changes,
so Docker serves the first build's answer for as long as the cache survives
while the registry moves on — and the rebuild that looks like it fixed that
did not. Measured: an image rebuilt the same day carried scc v0.23.0 into a
workspace whose settings.json registers the UserPromptSubmit hook v0.24.0
added, so every prompt of the session answered `unknown hook stage
"user-prompt"`. The three tools are pinned to build ARGs now; bumping an ARG
is what invalidates the layer. SCC_VERSION is the one with a contract to the
workspace it runs in — the other two answer to nobody here.

And a named volume mounted onto a path the image never created is created
root-owned: Docker copies an image path's ownership into a fresh volume only
when that path already exists. Without `/home/node/.claude` in the image, the
first write of every session was `EACCES ... mkdir
'/home/node/.claude/session-env'`, and the login it could not store read back
as "not logged in". The image creates and chowns it.

Verified: go build, golangci-lint run (0 issues), golangci-lint fmt --diff,
the full suite, and `scc validate` (10 validators, 0 findings). Two new tests
in internal/cli hold both halves against the seeded files rather than against
the prose explaining them.
…try file

`scc launch` splices both into the entry file the agent is about to load —
CodeGraph's under scc's own markers, RTK's under RTK's — and this checkout had
been carrying them uncommitted. Committing them is what makes every session
here start with the same guidance, rather than the first launch on a fresh
clone writing them again.

The bytes are the ones the binary writes; nothing in the prose above them
changed.
The skill wrote a gauntlet prompt for any goal, with a named artifact as the
bar — screenshot Nike, read the published piece. scc has nothing to
screenshot. What it has is the question design/ponytail-port.md closed without
answering: nothing measures whether the rules earn the bytes they cost in
every request of every session.

So the bar becomes an arm: two harness configurations running the same ticket,
with the critic blind to which is which, and the control checked for leaks —
the operator's global config is where the last one hid.
@protonspy
protonspy merged commit ac0983b into main Sep 16, 2026
4 checks passed
@protonspy
protonspy deleted the fix/devcontainer-pins-and-home branch September 16, 2026 16:46
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.

1 participant