Skip to content

container: persist the home-manager profile; strip the stale CLAUDE.md container doc - #194

Closed
alycda wants to merge 2 commits into
mainfrom
fix/entrypoint-probe-and-stale-claude-md
Closed

alycda wants to merge 2 commits into
mainfrom
fix/entrypoint-probe-and-stale-claude-md

Conversation

@alycda

@alycda alycda commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Two container-lifecycle bugs found while auditing the claude-home volume on 2026-09-30.

1. Activation re-runs on every start

docker/entrypoint.sh probes ~/.local/state/nix/profiles/home-manager, but
home-manager only writes its profile there when that directory already exists.
It never did, so home-manager fell back to /nix/var/nix/profiles/per-user/root,
which is baked into the image and gone on every --rm. CURRENT was always
empty:

  • activation (and the nix-env -e git-minimal man-db loop) ran on every start,
    not only when the baked generation changed;
  • any in-container home-manager switch was silently reverted by the next start.

Evidence: in a running container, per-user/root/home-manager-1-link is
timestamped at that container's start and is always generation 1.

Fix: mkdir -p /root/.local/state/nix/profiles before the probe. home-manager
then chooses the volume path and the existing probe is correct as written.
The Dockerfile's recovery recipe (rm -rf /root/.local/state/nix ...) already
covers this path.

2. Pre-#122 container doc still inside ~/.claude/CLAUDE.md

#122 moved the container doc to ~/.claude/rules/container-env.md and stopped
copying it over CLAUDE.md, but ~/.claude is the claude-home volume and the
sync script preserves everything outside the managed markers. Result on every
existing volume: the doc loads twice per session, and the stale copy still says
it is "re-copied on every container start".

Fix: the existing awk pass in agents.nix now skips from a legacy
# Machine: ... via Docker container H1 to the next H1 or EOF. Both the arm64
and 2012-MBP variants match. Inert on any CLAUDE.md that never had the heading.

Verification

  • sh -n docker/entrypoint.sh; nix-instantiate --parse on agents.nix.
  • awk extracted and run against a live 78-line CLAUDE.md: reduces to the
    managed block. Against a synthetic file with hand-written notes after the
    legacy body: notes survive, legacy body removed.
  • Not exercised: a full image rebuild + two consecutive container starts. The
    second start should print no >> activating home-manager generation line.

Base is main: develop is an unrelated history without these files.

🤖 Generated with Claude Code

alycda and others added 2 commits September 30, 2026 07:47
The start-up probe reads /root/.local/state/nix/profiles/home-manager to
decide whether the baked generation is already active. home-manager's
activate script only writes its profile there when that directory already
exists; otherwise it falls back to /nix/var/nix/profiles/per-user/root.
The directory never existed in the volume, so every activation landed
under /nix - which is baked into the image, not a volume - and vanished
with the container. CURRENT was empty on every start, activation re-ran
every start, and any in-container `home-manager switch` was reverted by
the next start without a word.

Evidence from a running container: per-user/root/home-manager-1-link is
timestamped at that container's start and is always generation 1.

Create the state dir before probing. home-manager then chooses it, the
profile persists in devhome, and the existing probe line is correct as
written. The alternative - probing the /nix path instead - would not have
helped, since that path does not survive --rm either.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Before #122 the Dockerfile and entrypoint copied docker/CLAUDE*.md over
~/.claude/CLAUDE.md. #122 moved that doc to ~/.claude/rules/container-env.md
and stopped the copy, but ~/.claude lives in the claude-home volume and the
sync script preserves everything outside the managed markers by design, so
the old body stayed. Every session on such a volume loads the doc twice,
and the stale copy still claims it is re-copied on every start.

Teach the existing awk pass to skip from a legacy `# Machine: ... via
Docker container` H1 to the next H1 or EOF. Both the arm64 and the 2012
MBP variants match; the legacy docs have only ## subsections, so the range
is bounded by the doc itself. On a machine whose CLAUDE.md never had the
heading the rule is inert. Verified against a live 78-line CLAUDE.md
(reduces to the managed block) and a synthetic file where hand-written
notes follow the legacy body (notes survive).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
@github-actions

Copy link
Copy Markdown
Contributor

⊕ Entity-level changes

docker/entrypoint.sh

Status Type Name
Δ orphan module-level

home-manager/modules/tools/agents.nix

Status Type Name
Δ binding claudeMdSync

Summary: 2 modified across 2 files (1 modified orphan)

functions and classes, not lines · sem

@alycda alycda closed this Oct 6, 2026
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