Dependency Upgrade & Fix Loop
MIT↓ 0 downloadsA single Claude Code agent brings your dependencies up to date in safe batches — patches, then minors, then one major at a time — fixing any breakage it introduces, gated by a hard build + test check every round until everything is current and green.
Topology
Disclosures
Everything below runs on your machine or inside the sandbox when you use this workflow. Mismatches between these declarations and the actual code block publishing.
Host hooks
Commands executed on YOUR host machine by Sandcastle lifecycle hooks.
None declared.
Sandbox hooks
Commands executed inside the sandbox container.
npm install
Network access
None beyond your package registry. The agent reads and writes only the local repository inside the sandbox; the `npm install` sandbox hook and the agent's reinstall step fetch packages from your declared registry.
Shell expansion
No shell-expansion blocks in prompt files.
Files
Diff vs the stock Sandcastle 0.12.0 template Dockerfile — green lines were added by the author, red lines were removed from stock.
+# Sandbox image for the Dependency Upgrade & Fix Loop workflow.+# Node 22 + git + the Claude Code CLI, running as a non-root `agent` user.+# Add your project's toolchain (python, go, ...) here if your build or suite+# needs it.FROM node:22-bookworm-# System dependencies.RUN apt-get update && apt-get install -y --no-install-recommends \git \curl \jq \ca-certificates \&& rm -rf /var/lib/apt/lists/*# Claude Code CLI (the agent runtime).RUN npm install -g @anthropic-ai/claude-code# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to# the host user via --build-arg to avoid permission errors on bind mounts.+# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it+# (the stock Sandcastle template pattern) — groupadd/useradd would collide with+# the existing IDs on a default build.ARG AGENT_UID=1000ARG AGENT_GID=1000-RUN groupadd --gid ${AGENT_GID} agent \- && useradd --uid ${AGENT_UID} --gid ${AGENT_GID} --create-home --shell /bin/bash agent+RUN groupmod -o -g ${AGENT_GID} node \+ && usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node-USER agent-WORKDIR /workspace+USER ${AGENT_UID}:${AGENT_GID}+WORKDIR /home/agent++# Sandcastle bind-mounts the worktree and sets the working directory at+# container start; the container just needs to stay alive until then.+ENTRYPOINT ["sleep", "infinity"]
Show full Dockerfile (highlighted)
# Sandbox image for the Dependency Upgrade & Fix Loop workflow.
# Node 22 + git + the Claude Code CLI, running as a non-root `agent` user.
# Add your project's toolchain (python, go, ...) here if your build or suite
# needs it.
FROM node:22-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
jq \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Claude Code CLI (the agent runtime).
RUN npm install -g @anthropic-ai/claude-code
# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to
# the host user via --build-arg to avoid permission errors on bind mounts.
# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it
# (the stock Sandcastle template pattern) — groupadd/useradd would collide with
# the existing IDs on a default build.
ARG AGENT_UID=1000
ARG AGENT_GID=1000
RUN groupmod -o -g ${AGENT_GID} node \
&& usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node
USER ${AGENT_UID}:${AGENT_GID}
WORKDIR /home/agent
# Sandcastle bind-mounts the worktree and sets the working directory at
# container start; the container just needs to stay alive until then.
ENTRYPOINT ["sleep", "infinity"]
# Auth for the Claude Code agent.
# Run `claude setup-token` on your host to generate a token, then paste it here
# in your local .sandcastle/.env (never commit the real .env).
CLAUDE_CODE_OAUTH_TOKEN=
import { createSandbox, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
// A warm-sandbox dependency-upgrade loop. We create one sandbox, install
// dependencies once, then alternate between an agent pass (bump a batch of
// dependencies and fix any resulting breakage) and a hard build + test gate.
// Keeping the container warm across rounds means the base install is paid for
// once; each round only reinstalls what the agent actually changed.
const MAX_ROUNDS = 5;
await using sandbox = await createSandbox({
branch: "agent/deps",
sandbox: docker(),
hooks: {
sandbox: {
onSandboxReady: [{ command: "npm install", timeoutMs: 300_000 }],
},
},
});
for (let round = 1; round <= MAX_ROUNDS; round++) {
const pass = await sandbox.run({
name: `deps-round-${round}`,
agent: claudeCode("claude-opus-4-8", { effort: "high" }),
promptFile: ".sandcastle/prompt.md",
completionSignal: "<promise>ALL_GREEN</promise>",
});
// The agent signals every dependency is current and the suite is green.
if (pass.completionSignal) {
console.log(`Dependencies fully upgraded and green in round ${round}.`);
break;
}
// Hard gate: the project must build AND the suite must pass before the next
// round starts. A non-zero exit code is returned (not thrown), so we can
// fail loudly and hand the failure back to the agent next round.
const build = await sandbox.exec("npm run build");
if (build.exitCode !== 0) {
console.log(
`Round ${round}: build is red — the next round will fix it.\n${build.stdout}\n${build.stderr}`,
);
continue;
}
const tests = await sandbox.exec("npm test");
if (tests.exitCode !== 0) {
console.log(
`Round ${round}: tests are red — the next round will fix it.\n${tests.stdout}\n${tests.stderr}`,
);
continue;
}
console.log(`Round ${round}: bumped dependencies, build and tests green.`);
}
Upgrade dependencies and keep the build green
You are an autonomous maintenance engineer working inside this repository. Your goal is to bring the project's dependencies up to date without leaving the build or the test suite broken.
Work in small, safe batches. Each time you run:
- Survey what is outdated. Use the project's package manager to see available
updates (e.g.
npm outdated, or readpackage.jsonagainst the lockfile). - Bump a conservative batch, in this priority order:
- First, all safe patch updates together.
- Then safe minor updates together.
- Only then a single major update at a time — never two majors in one round, because their breakage is hard to untangle.
- Reinstall so the lockfile is consistent (e.g.
npm install). - Address any breakage the bumps introduce:
- Follow each dependency's migration/changelog for renamed APIs, moved exports, or changed defaults.
- Update call sites, config, and types as needed. Prefer the smallest change that adapts the code to the new version — do not rewrite unrelated code.
- Run
npm run buildand thennpm testyourself and make sure both pass. - Commit with a clear message like
chore(deps): bump <packages> and fix <what>.
Keep each round tightly scoped so every commit is easy to review. If a specific
major upgrade proves too risky (breaking API with no clean migration), skip it,
leave a short // TODO(deps): note explaining why, and move on rather than
leaving the tree broken.
After your changes, a hard gate runs npm run build and npm test. If either
is red, the next round will hand you the failure to fix.
When every dependency is on its latest compatible version (or the only
remaining updates are ones you have consciously skipped and documented) and
both the build and the test suite pass, output the exact line
<promise>ALL_GREEN</promise> and stop.
README
Dependency Upgrade & Fix Loop
Stale dependencies rot quietly — until a security advisory or a blocked feature forces a painful, all-at-once upgrade. This workflow keeps that debt from piling up by letting a single Claude Code agent do the tedious part: bump your dependencies in safe batches and repair whatever the bumps break, all behind a hard build-and-test gate.
What it does
On each round the agent surveys what is outdated and bumps a conservative
batch in priority order — all safe patches together, then minors, then a
single major at a time. It reinstalls, then works through the fallout:
renamed APIs, moved exports, changed defaults, type errors. It follows each
package's migration notes and makes the smallest change that adapts your code to
the new version. If a major upgrade is genuinely too risky, it skips it, leaves a
// TODO(deps): note explaining why, and keeps the tree green rather than
leaving it broken. When everything is current and passing, it emits
<promise>ALL_GREEN</promise> and stops.
How it works
main.ts creates one warm Docker sandbox with createSandbox() and installs
dependencies once via an onSandboxReady hook. It then loops up to five rounds.
Every round is an agent pass followed by a hard gate that runs npm run build
and then npm test through sandbox.exec(). If either is red, the failure is
handed back so the next round can fix it — the loop never stacks broken upgrades.
Because the container stays warm, the base install is paid for once, not per
round.
The topology is a tight loop: install → bump deps + fix breakage → verify (build + test) → back to bump.
Requirements
Set CLAUDE_CODE_OAUTH_TOKEN in .sandcastle/.env (run claude setup-token on
your host). Your repo should expose a working npm run build and npm test;
adjust the prompt if your stack uses a different package manager or scripts.
Build the image once with npx @ai-hero/sandcastle docker build-image, then run
it with npx tsx .sandcastle/main.ts. Work lands on the agent/deps branch for
review before you merge.