Two recipe-shape gaps caught by the all-SDK lint+audit dogfood:
1. `cargo install --root /caches/cargo cargo-audit cargo-deny` lost its
binaries at runtime because /caches/cargo is volume-shadowed by the
host bind mount. Fix: install with `--root /usr/local` so the bins
land in /usr/local/bin (root-owned, not volume-shadowed). Required
USER root briefly to write to /usr/local; reverts to crafter after.
2. `mypy --strict` against any project that imports requests/PyYAML/
setuptools fails with "Library stubs not installed" exit 1 because
pipx-installed mypy lives in its own venv and doesn't see the
stubs. Fix: `pipx inject mypy types-requests types-PyYAML
types-setuptools` so the stubs land in mypy's venv.
The agent-generated Dockerfile accumulated PATH via 6+ layered ENV
PATH= statements, and my own GOPATH-fix edit (commit 6cd5990) wrote
a literal-expanded PATH that clobbered the swift/kotlin/gradle/bun/
cargo entries. Result: cargo unreachable from crafter user (caught
by the 14-SDK queue dogfood — exit 127 'Permission denied' on cargo
build).
Fix: a final ENV PATH= line right before the CMD that sets PATH to
a clean, comprehensive list of every toolchain bin. Overrides any
drift above. Includes:
- /home/crafter/.local/bin (pipx tools: ruff, mypy, pytest, pip-audit, uv, semgrep)
- /home/crafter/.composer/vendor/bin (phpstan, phpunit)
- /home/crafter/.local/share/gem/ruby/3.1.0/bin (bundler-audit, rubocop)
- /home/crafter/.bun/bin (bun)
- /home/crafter/go/bin (govulncheck, staticcheck)
- /home/crafter/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin (cargo, rustc, clippy, rustfmt)
- /caches/cargo/bin (cargo install artifacts; volume-mounted)
- /opt/swift/usr/bin (swift)
- /opt/kotlin/bin (kotlinc)
- /opt/gradle/bin (gradle)
- /usr/local/go/bin (go)
- system bins
Once this rebuild lands, the rust recipes can drop the per-recipe
PATH= prefix the workaround used.
Two bugs caught by the first real concurrent dogfood (15 SDK build jobs
queued at once for clawdforge):
1. Concurrent fetch race — multiple jobs from same project all called
`git fetch +refs/heads/*:refs/heads/*` simultaneously. Git refuses
to fetch into a local branch ref that another worktree has checked
out, so jobs after the first failed fast with:
fatal: refusing to fetch into branch 'refs/heads/main' checked
out at '/workspace/clawdforge/<other-job-id>'
2. Worktree-from-local-branch — `worktree add ... main` reserved the
local branch ref to the worktree, blocking subsequent fetches even
when the lock above wasn't held.
Fix:
- Per-project asyncio.Lock around the materialize() body (clone +
fetch + worktree-add). Different projects still parallelize; same
project serializes through the cache dir.
- Fetch into remote-tracking refs only:
+refs/heads/*:refs/remotes/origin/*
Local refs/heads/* are never written, so no worktree can hold them.
- worktree add uses `--detach origin/<branch>` so each worktree is at
the remote-tracking ref. Multiple detached worktrees share the same
remote ref without conflict.
The recipe itself runs outside the lock, so concurrency=4 still
parallelizes the actual build/test work — only the brief
materialize step (clone or fetch + worktree create) serializes.
Tests still 6/6 green on test_runner.py (uses _StubWorkspace, not the
real WorkspaceManager, so it's unaffected — the real exercise is the
live re-queue against fixed code).
Code-work prompts (read CVE/lint context, draft a unified diff that
verifies cleanly against the failing recipe) reward Opus's longer
context + careful reasoning. Cauldron-style high-frequency Sonnet calls
are unaffected — this only changes what crafting-table's patcher asks
clawdforge to use for its drafted-patch sessions.
Pairs with clawdforge dbbead2 which adds the optional `model` field on
POST /sessions and propagates ANTHROPIC_MODEL into the acpx subprocess
env on both create + turn.
Override knob: CRAFTING_PATCHER_MODEL env (e.g. "sonnet" if cost > quality).
Tests: 16/16 patcher tests still green.
- Dockerfile: pip-install requirements.txt and copy crafting_table/ into
/app, switch CMD from /bin/bash to uvicorn server (port 8810). pip lands
in /usr/local/bin so the crafter user runs uvicorn without elevation.
- compose.yml: replace smoke.sh entrypoint with the API server command;
bind 192.168.0.5:8810:8810 (LAN-only); switch named volumes to real
Lucy appdata paths so /data + /workspace + /caches survive recreate.
env_file marked optional so a fresh checkout boots without copying
.env.example.
- README.md: tick steps 1-4 done, document API surface table, add
curl-based quickstart (mint token → register project → kick off job →
poll → stream log), and an architecture-notes section covering the
recipe-immutability snapshot, process-group SIGTERM/SIGKILL escalation,
WAL+single-writer trade-off, and the recipe-security stance.
Smoke remains runnable on demand:
docker compose run --rm crafting-table /usr/local/bin/smoke.sh