Skip to content

Stale primary checkout silently ships an out-of-date /usr/bin/wp-codebox (no skew detection / no auto-rebuild) #1222

@chubes4

Description

@chubes4

Summary

On a live install, /usr/bin/wp-codebox is a symlink to the workspace primary checkout's built dist (/var/lib/datamachine/workspace/wp-codebox/packages/cli/dist/index.js), not an npm-global package. There is currently nothing that detects or prevents that primary from drifting behind origin/main, and nothing that rebuilds dist/ when it does. The result: a fixed bug can stay broken in production indefinitely because the binary points at stale, never-rebuilt source.

This issue tracks adding skew detection and/or auto-rebuild so a stale primary can't silently serve an out-of-date CLI.

How it bit us (concrete incident)

Production was emitting plain-text errors on the recipe-run --json error path instead of a JSON envelope, which broke a PHP caller doing json_decode(stdout) with did not return valid JSON: Syntax error. Repro on the stale binary:

$ wp-codebox recipe-run --recipe /tmp/x.json --artifacts /tmp/a --json
Unsupported recipe schema in /tmp/x.json      # plain text, not JSON, exit 1
$ wp-codebox recipe-run --json
Missing required option: --recipe              # plain text, not JSON

The actual code fix for this had already landed on main in commit 3118a438 ("Add command artifact schema contracts (#1059)", 2026-06-16) — cli-main.ts's error sink already consults wantsJsonOutput(args) and calls writeJsonFailure(...), and regression tests (cli-json-failure-smoke, cli-unsettled-command-smoke) are in the check smoke group.

But the primary checkout was pinned at eb9a717f (2026-06-15) with a 2026-06-15 dist/, i.e. one day before the fix:

  • git merge-base --is-ancestor 3118a438 HEADNO (fix not in primary source)
  • grep -c "wantsJsonOutput(args)" packages/cli/src/cli-main.ts0
  • dist/cli-main.js built 2026-06-15, no wantsJsonOutput

So /usr/bin/wp-codebox kept serving the pre-fix behavior even though main had been fixed. Meanwhile the deployed WP plugin's recipe-writer had moved ahead, which is what surfaced the Unsupported recipe schema mismatch in the first place.

Resolution applied to the affected host (manual, not a code change)

  1. workspace git pull wp-codebox --allow-primary-refresh (fast-forward eb9a717f700fe40d, clean tree). Fix 3118a438 now in source.
  2. npm run build → fresh dist/ carrying wantsJsonOutput in dist/cli-main.js.
  3. Verified the live /usr/bin/wp-codebox:
    • recipe-run --json (missing recipe) → JSON envelope to stdout, exit 1 ✅
    • recipe-run --recipe <bad-schema> --json → JSON envelope to stdout, exit 1 ✅
    • recipe-run (no --json) → plain text to stderr, stdout empty, exit 1 (regression preserved) ✅

That fixed this host, but nothing stops it recurring on any host whose primary drifts.

Proposed work (this issue)

Pick one or both:

  1. Skew detection in doctor. Have wp-codebox doctor compare the running binary's source revision against origin/main (or the configured tracked ref) and flag/warn when the primary that backs the binary is behind, or when dist/ is older than src/. The binary already reports a source fingerprint in doctor — extend it to a freshness assertion.

  2. Auto-rebuild on primary refresh. When the primary checkout is refreshed (--allow-primary-refresh), rebuild dist/ so the symlinked binary can't lag its own source. At minimum, fail loudly if dist/ is stale relative to src/.

Acceptance criteria

  • A stale primary (HEAD behind tracked ref, and/or dist/ older than src/) is detected and surfaced, not silently served.
  • Refreshing the primary leaves /usr/bin/wp-codebox running the refreshed source (either by rebuild or by a hard error that forces one).
  • No behavior change when the primary is already fresh.

Notes

  • This is an operational/tooling gap, not a defect in the CLI's runtime behavior — the JSON-envelope fix itself is already correct and tested on main.
  • The /usr/bin/wp-codebox → primary-dist/index.js symlink model is the load-bearing detail: it means "deploy" and "binary freshness" are decoupled, so a fresh deploy of the plugin does not imply a fresh CLI binary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions