PR Description Writer
MIT↓ 1 downloadsA single Claude Code agent reads the diff and commit history of your branch against its base, then writes a complete, reviewer-ready PR_DESCRIPTION.md — summary, what changed, why, testing, risk, and a reviewer checklist — grounded entirely in the real diff.
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.
None declared.
Network access
None. The agent operates only on the local repository inside the sandbox.
Shell expansion
Prompt files contain !`command` blocks — the agent CLI executes these commands at prompt-load time. They are highlighted amber in the prompt files below.
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 PR Description Writer workflow.+# Mirrors the stock Sandcastle template: Node 22, git (the prompt diffs branches),+# and the Claude Code CLI, running as a non-root `agent` user.FROM node:22-bookworm-# System dependencies.+# System dependencies. `git` powers the diff/log shell-expansion blocks.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 PR Description Writer workflow.
# Mirrors the stock Sandcastle template: Node 22, git (the prompt diffs branches),
# and the Claude Code CLI, running as a non-root `agent` user.
FROM node:22-bookworm
# System dependencies. `git` powers the diff/log shell-expansion blocks.
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 { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
// The branch your PR targets. The prompt diffs the current HEAD against this
// branch to understand what changed. Override it for release/develop/etc.
const BASE_BRANCH = "main";
// A single Sonnet agent reads the diff and commit history between BASE_BRANCH
// and HEAD (via `git diff` / `git log` shell-expansion blocks in the prompt) and
// writes a complete PR_DESCRIPTION.md. One pass is enough — maxIterations: 1.
const result = await run({
name: "pr-description-writer",
agent: claudeCode("claude-sonnet-4-6"),
sandbox: docker(),
promptFile: ".sandcastle/prompt.md",
promptArgs: { BASE_BRANCH },
maxIterations: 1,
});
console.log(
`Wrote PR_DESCRIPTION.md in ${result.commits.length} commit(s) on ${result.branch}.`,
);
Write the pull request description
You are a senior engineer writing the pull request description for the change on
this branch, targeting {{BASE_BRANCH}}. Base your writeup entirely on the actual
diff and commit history below — do not invent changes that are not there.
Commits on this branch
!git log {{BASE_BRANCH}}..HEAD --oneline
Full diff against {{BASE_BRANCH}}
!git diff {{BASE_BRANCH}}...HEAD
Your task
Write PR_DESCRIPTION.md at the repository root with these sections:
- Summary — one or two sentences a reviewer can read in ten seconds: what this PR does and why it exists.
- What changed — a tight bulleted list of the concrete changes, grouped by area (feature / refactor / fix / tests / docs). Reference files or modules.
- Why — the motivation and context. What problem does this solve? Link the reasoning to the actual code where you can.
- Testing — how the change was or should be verified (tests added, manual steps, edge cases covered).
- Risk & rollout — the blast radius, anything reviewers should scrutinize, migrations, feature flags, or backwards-compatibility notes.
- Reviewer checklist — a short markdown checkbox list of what a reviewer should confirm before merging.
Be specific and concise. If the diff is large, lead with the most important changes. If something in the diff looks risky or incomplete, call it out honestly in the Risk section rather than glossing over it.
Commit the file with a message like docs: add PR description.
README
PR Description Writer
Stop writing pull request descriptions from scratch. Point this workflow at your
branch and it produces a complete, reviewer-ready PR_DESCRIPTION.md — grounded
entirely in the real diff, not in vague guesses about what you "probably" changed.
What it does
A single Claude Code agent reads the commit history and the full diff between your branch and its base, then writes a structured description with six sections:
- Summary — the ten-second version a reviewer reads first.
- What changed — a tight, grouped list of the concrete changes.
- Why — the motivation and context behind the change.
- Testing — how it was or should be verified.
- Risk & rollout — blast radius, migrations, and what to scrutinize.
- Reviewer checklist — a checkbox list to run before merging.
Because it works from the actual diff, it won't invent changes that aren't there — and it will honestly flag anything in the diff that looks risky or incomplete.
How it works
main.ts runs one Sonnet agent for a single pass (maxIterations: 1). The prompt
seeds the model with !`git log {{BASE_BRANCH}}..HEAD --oneline` and
!`git diff {{BASE_BRANCH}}...HEAD` shell-expansion blocks — which is why the
manifest discloses shell expansion. BASE_BRANCH is passed as a prompt argument
and defaults to main; change it at the top of main.ts for release or develop
branches. The topology is a single node: diff + log → PR description. Nothing
leaves the sandbox — there is no network access.
Requirements
Set CLAUDE_CODE_OAUTH_TOKEN in .sandcastle/.env (run claude setup-token).
Check out the branch you want documented and set BASE_BRANCH in
.sandcastle/main.ts if it isn't main. Build the image once with
npx @ai-hero/sandcastle docker build-image, then run
npx tsx .sandcastle/main.ts. The finished description lands at
PR_DESCRIPTION.md, ready to paste into your PR.