Verifying the build scripts skill

Drive this repo's build/release scripts end-to-end without touching the network.

by anthropics·MIT license·★ 8,221 Stars on the repo·GitHub ↗

Use now

Files of Verifying the build scripts

anthropics/main1 file shown
SKILL.md
Show the full text80 lines

Verifying the build scripts

The SDK itself is a library (drive it through import claude_agent_sdk), but the scripts/ directory is a set of CLIs run by the release workflows. Verify them by running them, not by importing them.

NEVER run the real installer

scripts/download_cli.py shells out to curl https://claude.ai/install.sh | bash (and the PowerShell equivalent). Running it for real overwrites the claude binary on the developer's machine. Do not run bash install.sh, and do not invoke download_cli.py with latest/stable (or any case variant) against the real network.

Instead, put stub curl / bash / powershell on a temp PATH and drive the script under env -i with an isolated HOME (so find_installed_cli() cannot discover the real binary and copy it into src/claude_agent_sdk/_bundled/).

The harness

SB=$(mktemp -d); mkdir -p $SB/stub $SB/home $SB/repo/src/claude_agent_sdk
cp scripts/{download_cli.py,build_wheel.py,update_cli_version.py,_cli_version_validation.py} $SB/repo/scripts/
printf '__cli_version__ = "2.1.208"\n' > $SB/repo/src/claude_agent_sdk/_cli_version.py

cat > $SB/stub/curl <<'EOF'
#!/bin/bash
out=""; prev=""; for a in "$@"; do [ "$prev" = "-o" ] && out="$a"; prev="$a"; done
echo "[stub curl] argv: $*" >> "$MARKER"
[ -n "$out" ] && printf '%b' "${CURL_BODY:-#!/bin/bash\necho hi\n}" > "$out"
exit ${CURL_EXIT:-0}
EOF
cat > $SB/stub/bash <<'EOF'
#!/bin/bash
{ echo "[stub bash] argc=$#"; for a in "$@"; do echo "    <$a>"; done; } >> "$MARKER"
EOF
chmod +x $SB/stub/*

cd $SB/repo
env -i HOME=$SB/home PATH=$SB/stub:/usr/bin:/bin MARKER=$SB/m \
    CLAUDE_CLI_VERSION=2.1.208 .venv/bin/python scripts/download_cli.py

run_command() captures the child's output, so the stubs must log to a $MARKER file — printing to stderr is swallowed.

Flows worth driving

  • update_cli_version.py <version> — the whole validator surface is reachable here: a concrete version writes the file; latest/stable, v2.1.207, next, 2.1, and a quote-breakout string each exit 1 with a distinct message and leave the file untouched.
  • build_wheel.py --skip-sdist — reads the pin, then chains into download_cli.py. Rewrite _cli_version.py in the sandbox to a moving tag / single quotes / garbage / delete it: each must fail before any subprocess is spawned. --cli-version <v> bypasses the pin (intentional escape hatch; the release workflow does not use it).
  • download_cli.py — set CURL_BODY to an HTML error page or an empty string to prove the body check refuses it with no retry; empty the PATH to prove a missing curl fails fast in one attempt; CURL_EXIT=22 to prove a genuine transient failure still retries 3×.
  • The Windows path is unreachable on Linux. It is behind platform.system() == "Windows". Drive it with a small runner that patch.object(m.platform, "system", return_value="Windows") and a stub powershell that reads the -Command text and honours $env:CLAUDE_CLI_INSTALL_SCRIPT. This is the one place where forcing the platform is legitimate.

Gotchas

  • ${CURL_BODY:-default} treats an empty body as unset. Use a separate stub that does : > "$out" to test the empty-body branch.
  • The retry path really sleeps (jitter up to 5s, then 2s + 4s). A full three-attempt run takes ~10s; budget for it rather than assuming a hang.
1---
2name: verify
3description: Drive this repo's build/release scripts end-to-end without touching the network. Use when verifying changes to scripts/download_cli.py, scripts/build_wheel.py, scripts/update_cli_version.py, or scripts/_cli_version_validation.py.
4---
5 
6# Verifying the build scripts
7 
8The SDK itself is a library (drive it through `import claude_agent_sdk`), but
9the `scripts/` directory is a set of **CLIs** run by the release workflows.
10Verify them by running them, not by importing them.
11 
12## NEVER run the real installer
13 
14`scripts/download_cli.py` shells out to `curl https://claude.ai/install.sh | bash`
15(and the PowerShell equivalent). Running it for real **overwrites the `claude`
16binary on the developer's machine**. Do not run `bash install.sh`, and do not
17invoke `download_cli.py` with `latest`/`stable` (or any case variant) against
18the real network.
19 
20Instead, put stub `curl` / `bash` / `powershell` on a temp `PATH` and drive the
21script under `env -i` with an isolated `HOME` (so `find_installed_cli()` cannot
22discover the real binary and copy it into `src/claude_agent_sdk/_bundled/`).
23 
24## The harness
25 
26```bash
27SB=$(mktemp -d); mkdir -p $SB/stub $SB/home $SB/repo/src/claude_agent_sdk
28cp scripts/{download_cli.py,build_wheel.py,update_cli_version.py,_cli_version_validation.py} $SB/repo/scripts/
29printf '__cli_version__ = "2.1.208"\n' > $SB/repo/src/claude_agent_sdk/_cli_version.py
30 
31cat > $SB/stub/curl <<'EOF'
32#!/bin/bash
33out=""; prev=""; for a in "$@"; do [ "$prev" = "-o" ] && out="$a"; prev="$a"; done
34echo "[stub curl] argv: $*" >> "$MARKER"
35[ -n "$out" ] && printf '%b' "${CURL_BODY:-#!/bin/bash\necho hi\n}" > "$out"
36exit ${CURL_EXIT:-0}
37EOF
38cat > $SB/stub/bash <<'EOF'
39#!/bin/bash
40{ echo "[stub bash] argc=$#"; for a in "$@"; do echo " <$a>"; done; } >> "$MARKER"
41EOF
42chmod +x $SB/stub/*
43 
44cd $SB/repo
45env -i HOME=$SB/home PATH=$SB/stub:/usr/bin:/bin MARKER=$SB/m \
46 CLAUDE_CLI_VERSION=2.1.208 .venv/bin/python scripts/download_cli.py
47```
48 
49`run_command()` captures the child's output, so the stubs must log to a
50`$MARKER` file — printing to stderr is swallowed.
51 
52## Flows worth driving
53 
54- **`update_cli_version.py <version>`** — the whole validator surface is
55 reachable here: a concrete version writes the file; `latest`/`stable`,
56 `v2.1.207`, `next`, `2.1`, and a quote-breakout string each exit 1 with a
57 distinct message and leave the file untouched.
58- **`build_wheel.py --skip-sdist`** — reads the pin, then chains into
59 `download_cli.py`. Rewrite `_cli_version.py` in the sandbox to a moving tag /
60 single quotes / garbage / delete it: each must fail *before* any subprocess
61 is spawned. `--cli-version <v>` bypasses the pin (intentional escape hatch;
62 the release workflow does not use it).
63- **`download_cli.py`** — set `CURL_BODY` to an HTML error page or an empty
64 string to prove the body check refuses it with no retry; empty the `PATH` to
65 prove a missing `curl` fails fast in one attempt; `CURL_EXIT=22` to prove a
66 genuine transient failure still retries 3×.
67- **The Windows path is unreachable on Linux.** It is behind
68 `platform.system() == "Windows"`. Drive it with a small runner that
69 `patch.object(m.platform, "system", return_value="Windows")` and a stub
70 `powershell` that reads the `-Command` text and honours
71 `$env:CLAUDE_CLI_INSTALL_SCRIPT`. This is the one place where forcing the
72 platform is legitimate.
73 
74## Gotchas
75 
76- `${CURL_BODY:-default}` treats an *empty* body as unset. Use a separate stub
77 that does `: > "$out"` to test the empty-body branch.
78- The retry path really sleeps (jitter up to 5s, then 2s + 4s). A full
79 three-attempt run takes ~10s; budget for it rather than assuming a hang.
80 

Discussion