fix: account for install source and replaced files - #595
Merged
Merged
Conversation
Managed files are copied out of $SCRIPT_DIR, which is the operator's working checkout rather than a release artifact. Two silent failures followed from that. A checkout parked on an old branch reinstalls stale artifacts on every run while reporting success. A site was pinned to claude-cli/2.1.75 by repeated upgrades from a checkout 79 commits behind main, long after main shipped 2.1.259, and Anthropic then rejected the session for an unsupported client version. A managed file edited on the site was overwritten with no backup and no notice, so the edit was unrecoverable. Add lib/install-source.sh. install_source_report_integrity describes the install source at the top of setup and upgrade when it is not a clean default branch. It warns rather than aborts, because running from a feature worktree is how this tool is developed; the defect was that the operator could not tell. install_source_sync_managed_file backs a diverged destination up to .backup.<ts> before replacing it and reports the backup path, matching the convention already used for AGENTS.md. Route both Claude Code auth plugin installers through the helper, replacing a bare cp in the setup path and a cmp-only no-op guard in the upgrade path. Cover both behaviors against real git checkouts and a real filesystem.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every managed file this tool writes to a site is copied out of
$SCRIPT_DIR. That is not a release tarball — it is the operator's working checkout, and whatever is in that working tree whensetup.shorupgrade.shruns is what lands on the site. Two failure modes follow, and both were silent.Stale source. A checkout parked on an old feature branch faithfully reinstalls artifacts predating fixes that landed on the default branch months ago, and the operator sees a successful upgrade.
Observed in the wild: a site pinned to
claude-cli/2.1.75by repeated upgrades run from a checkout 79 commits behindmain, long afterb6ca654shipped2.1.259onmain. Anthropic then rejected the session:The error points at the site and at the locally installed
claudebinary (which was current, at 2.1.269). Neither was the cause. The installer was. There was no signal anywhere in the upgrade output that the artifacts came from a tree 79 commits stale.Clobber. A managed file hand-edited on the site was overwritten with no backup and no notice, so the edit evaporated on the next run. In the case above this compounded the first problem: manually bumping the site's plugin appeared to work, then silently reverted on the next upgrade.
Neither is fixed by pinning a better value somewhere. They are fixed by making the installer honest about where its inputs came from and what it destroyed.
Change
New
lib/install-source.shwith two public functions.install_source_report_integrityruns at the top ofsetup.shandupgrade.sh. Against the checkout described above it now prints:It warns; it never aborts. Developing wp-coding-agents means running
setup.shandupgrade.shfrom a feature-branch worktree on purpose, and a hard failure there would break the tool's own development loop. The defect was never that operators install from a branch — it is that they could not tell they had. No fetch is performed: setup and upgrade should not acquire a network dependency to report on local state, and the warning says the behind-count is measured as of the last fetch.install_source_sync_managed_file <source> <dest> <label>copies a managed artifact while preserving anything it would destroy. Identical is a no-op with no output and noUPDATED_ITEMSentry; a new destination is a plain install; a diverged destination is backed up to.backup.<ts>alongside, then replaced, with both the install and the backup path reported.The backup is unconditional on divergence rather than gated on a heuristic for "was this a local edit". There is no reliable way to distinguish an operator's hand-edit from an artifact left by an older release, and guessing wrong destroys work. A cheap redundant backup is the correct trade, and the AGENTS.md path in this repo already settled on the same convention.
Both Claude Code auth plugin installers now route through the helper, replacing a bare
cpinruntimes/opencode.shand acmp-only no-op guard inupgrade.shthat still overwrote local edits whenever content differed.Verification
Two new tests drive real git checkouts and a real temp filesystem, asserting resulting bytes on disk:
tests/install-source-managed-file-sync.sh— fresh install, identical re-run is a true no-op (mtime unchanged), diverged destination is recoverable from the backup and the backup path is reported, dry run changes nothing, missing source does not clobber.tests/install-source-integrity-report.sh— clean default branch is silent, behind-default-branch is reported by name and count, uncommitted paths are reported, a non-git source is silent rather than fatal.Full shell suite green.
tests/opencode-local-plugin-path.shcaught a genuine break from this change (isolated sourcing ofruntimes/opencode.shwithout the new lib) and is fixed here; it already asserts the plugin is really installed to the site path, so it covers the changed path end to end.tests/ci-coverage.shcaught that the new tests were not wired into CI; added toshell.yml.tests/datamachine-worker.shfails locally on an absolutewppath, and fails identically onorigin/main— pre-existing and unrelated.shellcheck is clean on all new files.
Note for reviewers
tests/upgrade-opencode-auth-plugin-sync.shasserts that string literals such assource_path="$SCRIPT_DIR/..."appear inupgrade.sh. It passes while the installer ships a months-stale artifact, because it never inspects installed output. Left unchanged as out of scope, but it is not covering the behavior its name implies.AI assistance
Investigated and implemented with Claude (claude-sonnet-4-6) via opencode. The model traced the 400 to the spoofed
user-agentheader, established that the version pin was already fixed onmainand that the install source was a stale checkout, designed and wrotelib/install-source.shand both test files, and ran the full shell suite. Reviewed and directed by @chubes4.