fix(agents): enable container mode for designer + design-reviewer #67
No reviewers
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
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!67
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/designer-container-mode"
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?
Summary
The
designeranddesign-revieweragents havepenpot_mcp: true, sothe Agent SDK is configured to spawn
penpot-mcpas an MCP subprocess.That binary ships only in
claude-hooks:dev(baked in by #65 /commit
41dc22b) — it is deliberately not installed on the host.Without
container: { enabled: true }, the SDK tries to spawnpenpot-mcpon the host, fails with ENOENT, and the agent's MCPsurface is missing the
mcp__penpot__*namespace at runtime. Surfacedwhile running the operational smoke on #62 after PR #66 landed the
unit-level wiring smoke.
Closes the last implicit AC on #56 ("container enabled with the
claude-hooks:devimage") — this flag wasn't flipped when theagents landed in
fd59eb0.Scope
Minimal and deliberate: only
designeranddesign-reviewerget theflag.
boss/dev/reviewerstay in host mode (their containersexist but go unused today) — flipping those is a separate decision.
Test plan
just qapasses (234 tests).scripts/smoke-creds.sh designer design-reviewergreen — both containers have
penpot-mcp+penpot_mcpimport.claude-hooksservice and re-togglearea:designon #62 to run the operational smoke end-to-end.Container-mode agents hang indefinitely on their first Anthropic API call when the bind-mount dir contains only `.credentials.json`. The Claude CLI needs a populated `.claude.json` with `oauthAccount` to bind the credentials to an org; in host mode the CLI writes its own (the isolated config dir is writable), but the container's mount is read-only, so the write fails silently and the API call stalls — observed as 10+ minutes of zero progress while the CLI waits on a response the server is holding. Add `seedContainerClaudeJson(credsDir)` (container.ts) that copies just the `oauthAccount` field from `~/.claude/.claude.json` into the bind dir if no `.claude.json` is there yet. Idempotent, writes `{}` when no `oauthAccount` is on the host (fail-loud instead of hang), no-op when the host file is missing entirely. Wire it into `main.ts` startup, once per distinct `credentialsHostDir` across the agents that have `container.enabled: true`. Runs after webhook config loads and before workers register — tasks see a valid bind dir on first dispatch. Unit tests in `container.test.ts` cover the five branches: copy, idempotent overwrite-skip, empty-stub fallback, missing-host no-op, and dir-creation. Parameterised via an optional `userClaudeJsonPath` so tests don't depend on the runner's real home dir. Surfaced while running the #56 operational smoke on #62 — the designer task hung on Opus 4.7/4.6 until this was seeded by hand. With the seed in place the task completed in ~2 min and posted a real diagnostic comment listing Penpot-side blockers (expired OIDC cookie + missing shape-creation MCP tools).CI green (run #1579, 234 tests, 2m39s). ✅
config/agents.json— Correct and minimal."container": { "enabled": true }added todesigneranddesign-revieweronly;boss/dev/reviewerstay in host mode as intended.container.ts—seedContainerClaudeJson— Well-implemented. Idempotent early-return onexistsSync,mkdir({ recursive: true })before write, 0o600 mode enforced,statpost-write confirms actual mode in the log line. The no-op-with-warn design on a missing host file is the right call — a console warning that says "may hang" is far clearer than silently writing a{}stub that causes a different failure mode.container.test.ts— Five meaningful scenarios with no gaps: copyoauthAccount, idempotent (existing file wins even with a different host value), nooauthAccount→ write{}stub, missing host file → no-op, create missingcredentialsHostDir. Good explicit assertion in test 1 thatuserID/ unrelated fields are not leaked into the target file.main.ts— Startup seeding loop correctly deduplicates bycredentialsHostDirwith aSetbefore callingseedContainerClaudeJson. Errors propagate naturally from the callee (which handles its own gracefully withconsole.warn).Closes the last implicit AC on #56. Approved.
container.credentials_host_pathbackwards-compat in favor ofcredentials_host_dir#77