Bind-mount credentials directory, not file (survive claude login without container restart) #57
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#57
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
User story
As a maintainer, I want agent containers to pick up refreshed Claude OAuth credentials without a restart, so that every
claude loginon the host doesn't silently break every running agent container for the next 8 h.Context
Seen on #56 today. Sequence of events:
~/.config/claude-hooks/claude-credentials/.credentials.jsoninto/home/claude/.config/claude-code/.credentials.json. The mount binds to the inode at that path.claude login(or any refresh path that writes.new+rename) replaces the host file with a new inode.nlink=0but kept alive because the container has it mounted). Container sees stale, expired token forever.HTTP 401 · authentication_erroruntil the container is restarted.We worked around it today by
docker restart-ing boss/dev/reviewer. We also set up a systemd path unit (~/.config/systemd/user/claude-creds-mirror.{path,service}) that doescat src > dstto preserve the inode on the mirror file — that helps when the host-side mirror is in-place-rewritten, but it cannot rescue us whenclaude loginitself atomic-renames the source file upstream of the mirror.The root cause is: bind-mounting a file is bind-mounting an inode. Bind-mounting the containing directory sees path-level changes.
Acceptance criteria
Container config
config/agents.jsoncontainer spec takes a directory path (credentials_host_dir) instead of / alongsidecredentials_host_path, and the runtime mounts that directory read-only at/home/claude/.config/claude-code/(or wherever the SDK expects it).~/.config/claude-hooks/claude-credentials/contains only.credentials.json(already the case) — no accidental leakage of other secrets.Container launcher
src/workers/or whereverdocker runis assembled) to emit--mount type=bind,source=<dir>,target=<dir>,readonlyinstead of the current file-level mount.credentials_host_pathis set (old config), resolve to its parent dir automatically so nobody has to edit the config for the migration.Verification
docker inspect $container --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'should show the directory.~/.claude/.credentials.jsonvia any method (atomic rename, in-place, doesn't matter).~/.config/claude-hooks/claude-credentials/.credentials.json) gets its new inode.docker exec $container stat -c %i /home/claude/.config/claude-code/.credentials.jsonnow shows the host-current inode — no restart needed.Tests
credentials_host_pathemits a directory mount for the parent.scripts/that rewrites creds on the host, then callsdocker execon each agent container to verify the fresh inode is visible.Docs
CLAUDE.md: note that credentials are directory-mounted, andclaude loginon the host is picked up automatically by running containers.claude-creds-mirror.servicecomment in memory /scripts/setupto explain it's no longer strictly required (but still useful so the mirror stays in sync).Out of scope
References
docker restart-ed the trio).~/.config/systemd/user/claude-creds-mirror.{path,service}.config/agents.json→agents.*.container.credentials_host_path.~/.claude/projects/-home-charles-Workspace-claude-hooks/memory/agents.md.Dependencies
claude loginwithout manual intervention; tightens the reliability story for #56 and every future agent type.main.container.credentials_host_pathbackwards-compat in favor ofcredentials_host_dir#77