NEVER use curl -o /path/that/may/be/a/symlink ... — curl follows the symlink and writes through to the underlying target, which corrupts the source binary if the URL 404s. The symlink itself stays intact (which makes the corruption invisible to a ls -la check); only the target file is overwritten.
Why: On 2026-05-03T07:55-08:05 BST during the 22-spike Q&A-formulation suite install pre-flight, I gave Rich an install command:
curl -L -o ~/tools/inherit-spike-bin/cvc5 https://github.com/cvc5/cvc5/releases/download/cvc5-1.2.1/cvc5-Linux-x86_64-staticThe URL was wrong (missing .zip suffix; should have been cvc5-Linux-x86_64-static.zip per gh release view cvc5-1.2.1 --repo cvc5/cvc5). curl followed the 302 to a 9-byte “Not Found” page and wrote it. Critically: ~/tools/inherit-spike-bin/cvc5 was a SYMLINK to the actual binary at ~/tools/inherit-spike-bin/cvc5-1.2.1/cvc5-Linux-x86_64-static/bin/cvc5. curl wrote 9 bytes through the symlink, corrupting the binary. The symlink itself was untouched (still present + valid pointer; ls -la showed lrwxrwxrwx as before). Only when Rich tried to RUN the binary did the corruption surface (Not: command not found because bash tried to interpret the 9-byte “Not Found” text as a script).
Recovery: redownload + extract from the correct zip (29.4 MB; cvc5-Linux-x86_64-static.zip from cvc5-1.2.1 release), restore directory structure, recreate the symlink. Took ~10 min.
How to apply:
-
For binary downloads from GitHub releases / similar: download to a unique temp filename (
/tmp/<asset>-<sha>.zipor~/Downloads/<asset>.tar.gz), size-verify (ls -la <file>; expected size in MB-range or larger NOT 9 bytes / single-digit-KB), THENmvor extract to the canonical install location. -
NEVER use
curl -odirectly against a path that might be (or might become) a symlink — this includes any path inside~/tools/...or~/.cargo/bin/...where binaries are commonly symlinked. -
For replacing existing files: explicitly
rmthe destination first if it’s a symlink, then download to a fresh path. This makes the intent visible + auditable. -
For paste-safe install commands handed to Rich’s WSL terminal: add an explicit guard before the destructive write:
[ -L /path/to/target ] && { echo "BAIL: $target is a symlink; will corrupt source"; exit 1; } curl -L -o /path/to/target <url>Or simply prefer the safer pattern:
curl -L -o /tmp/staging-<asset> <url> ls -la /tmp/staging-<asset> # size-verify mv /tmp/staging-<asset> /path/to/target -
Verify URL by
gh release view <tag> --repo <owner>/<name>BEFORE handing the curl command to Rich. GitHub release asset names vary across versions; do NOT pattern-match from prior versions or guess. -
Always size-verify the download with
ls -la <file>BEFORE proceeding. A 9-byte “Not Found” page failing-silent is the canary; an explicit size check catches it.
This sub-rule applies broadly: any file-write operation that follows symlinks (curl, wget, cp, dd) carries this risk. The fix is paste-safety + size-verification, not avoiding the tools.
Parent memory: feedback_paste_safety_for_terminal_handoffs locked 2026-05-02 BST (S5+S6+S7 verify command + FIBO IRI for-loop + Postgres provisioning compound chain).
Sibling reinforcement 2026-05-03T08:50 BST — printf-with-embedded-\n-content STILL wraps when emitted inline. The 22-spike install pre-flight tried using printf '...\n...\n...' > /tmp/file for Catala / fastembed / owlready2 / cedar smoke scripts — all 4 failed because the > redirect ended up on a wrapped line, sending stdout to terminal not file. The robust fix when handing multi-line content to Rich’s WSL terminal is to author the file via Claude’s Write tool (which writes directly to Rich’s filesystem since they share the same machine), then have Rich just execute it (python /tmp/X.py or bash /tmp/X.sh). The Write tool emits no terminal text + has no wrap risk. Decision rule: any content with ≥1 embedded newline OR ≥80-char single line → Write tool, never inline printf/echo with > redirect.
Trigger context: install pre-flight for the 22-spike Q&A-formulation suite (per 2026-05-03-22-spike-q-and-a-formulation-suite.md v1.1; commit history shows the patch).