Two separate sandbox blockers surfaced when the user tried
`pnpm work decompose --execute`:
1. **Container died on exec** — our Dockerfile had:
- WORKDIR /workspace + CMD ["bash"]
- No `agent` user (sandcastle exec's as UID:GID it built with)
- node:22-bookworm-slim (missing some build deps the install
script wants)
Sandcastle expects:
- A non-root `agent` user with home at /home/agent (sandcastle
does `git config --global --add safe.directory /home/agent/workspace`,
which fails if the user doesn't exist or the container exited)
- ENTRYPOINT ["sleep", "infinity"] so the container survives
the gap between sandcastle creating it and exec'ing in
Replaced .sandcastle/Dockerfile with the shape `sandcastle init`
would generate (verified against
node_modules/@ai-hero/sandcastle/dist/InitService.js):
- node:22-bookworm (full, not slim) for build tooling
- apt-get installs git + curl + jq
- corepack-pinned pnpm@9
- ARG AGENT_UID=1000 + AGENT_GID=1000; sandcastle's
build-image passes the host's UID/GID by default
- `groupmod -o -g $AGENT_GID node` + `usermod -o ... node` —
the `-o` (non-unique) flag is required because macOS hosts
have UID:501 GID:20, and GID 20 collides with Debian's
`dialout` group in the base image (without -o, groupmod
fails with "GID '20' already exists")
- USER ${AGENT_UID}:${AGENT_GID}, then install Claude Code CLI
via the official installer
- ENV PATH includes /home/agent/.local/bin
- WORKDIR /home/agent (sandcastle overrides per-run anyway)
- ENTRYPOINT ["sleep", "infinity"] keeps the container alive
2. **"Not logged in · Please run /login"** inside the container —
Claude Code on macOS stores credentials in the Keychain, NOT in
~/.claude/.credentials.json. Sandcastle's bind-mount of ~/.claude
finds nothing usable. Documented the workaround:
- README.md "Sandcastle setup (one-time)" — macOS-specific
block with the `security find-generic-password ... > ~/.claude/.credentials.json`
one-liner + chmod 600 + the security trade-off (plaintext
file vs keychain isolation)
- docs/guides/runbook.md "Using Sandcastle → Prerequisites" —
step 3 (Authentication) gets a "macOS quirk" subsection with
the same extraction one-liner + the API-key fallback as the
alternative path
- scripts/work/{dispatch,decompose}.mjs — when the sandcastle
error matches /Not logged in|Please run \/login/ AND we're on
darwin, the dispatcher prints the keychain-extraction
commands + the API-key fallback inline above the generic
"See runbook" line, so future agents discover the fix at the
failure site
The image rebuilds clean (`pnpm exec sandcastle docker
build-image`) at ~1.95GB and the container survives sandcastle's
exec — confirmed by reaching the "Not logged in" stage (which is
the next-layer issue, not the Dockerfile issue).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
.sandcastle/
This directory holds prompt templates that the future orchestrator
(pnpm work dispatch in the sandcastle-dispatch-v1 epic) feeds to
sandcastle when dispatching
agents.
Prompt templates
| File | Role | Variables |
|---|---|---|
prd-eliciter.prompt.md |
Interview a human to produce a PRD draft | {{INITIAL_BRIEF}} |
adr-eliciter.prompt.md |
Interview a human to produce an ADR draft | {{INITIAL_PROPOSAL}} |
decomposer.prompt.md |
Turn a PRD into epic + story files | {{PRD_FILE_CONTENT}} |
implementer.prompt.md |
Execute a single task | {{TASK_FILE_CONTENT}} |
reviewer.prompt.md |
Review the implementer's diff | {{TASK_FILE_CONTENT}}, {{DIFF}} |
Convention: every prompt enforces "generators first"
Each prompt template starts with the same non-negotiable rule: the agent
must prefer pnpm turbo gen <kind> over hand-rolled scaffolding. This
applies to feature packages, events, jobs, realtime channels, optional
core packages, and atomic-design components. Hand-rolled code is only
acceptable when the generator's output doesn't cover the case — and even
then, the agent runs the generator first and modifies its output rather
than starting from scratch.
Environment
Configure runtime tokens via .env (gitignored). Copy .env.example
and fill values for the providers you use.
Build the sandbox image (one-time)
Sandcastle dispatches into a Docker image tagged sandcastle:<root-package-name>.
Build it once per clone before pnpm work dispatch --execute or
pnpm work decompose <id> --execute will work:
pnpm exec sandcastle docker build-image
# Tags: sandcastle:template-vertical
Rebuild after editing this Dockerfile:
pnpm exec sandcastle docker remove-image
pnpm exec sandcastle docker build-image
See docs/guides/runbook.md → Using Sandcastle → Prerequisites for the full setup.
Manual usage
Until the orchestrator ships, these templates are usable manually: copy
the relevant .prompt.md content into a Claude / Codex / other agent
session, fill the {{VARIABLE}} placeholders by hand, and run.