Repository navigation
Keep the installed CDD helpers current: sync on pull, self-reload, update - #126
Merged
Merged
Conversation
…date The install is a copy, so a merged helper fix changed nothing on the machine until someone re-ran install (the #124/#125 incident), open shells kept the old functions, and a curl install had no way to update. - Sync on pull: install, run from a CDD git checkout, writes a managed post-merge + post-rewrite hook into it. A pull that lands on the default branch with different helpers or adapters (cmp, never a version stamp) reinstalls both and prints one line. Other branches, amends and unchanged pulls do nothing. A foreign hook is never overwritten; under core.hooksPath nothing is written (both noted). - Self-reload: each public cdd-worktree* / cdd-state call re-sources its file when it changed on disk and re-dispatches, silently. - `bash ~/.cdd/tools/cdd-worktree.sh update`: on demand, a sparse blobless clone of upstream main's tools/; installs when it differs. cdd-state's unknown-verb / invalid-stage errors and the plan_written skew warning name it. The dispatch now exits explicitly, since update overwrites the script bash is running. New toolchain-sync gate (27 gates). install-smoke and adapter-bindings install from a non-git copy of tools/, so a check run never hooks the developer's checkout. Process doc §2.8, shell-helpers.md, ADR 0014, template BOOTSTRAP.md. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JyhD5C5M5Vsi6k7jzdzEwS
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JyhD5C5M5Vsi6k7jzdzEwS
drabaioli
commented
Oct 6, 2026
Comment on lines
+36
to
+51
| ## Staying current | ||
|
|
||
| The install is a copy, so merging a helper fix changes nothing on a machine until something reinstalls it — and a shell that sourced the helpers keeps the old functions even then. Three pieces close that gap ([ADR 0014](adr/0014-keep-the-install-current-by-syncing-on-pull.md)). None runs in the background or reaches the network on its own, and none prints anything while the install is current. | ||
|
|
||
| **The sync hook.** Run from a CDD git checkout, `install` writes a `post-merge` and a `post-rewrite` hook into the checkout's common hooks dir (`git rev-parse --git-path hooks`), which every worktree of the clone shares. "From a checkout" means `<toplevel>/tools/cdd-worktree.sh` is the running script (`-ef`, since the script path is logical and `--show-toplevel` physical) — never the installed copy, nor a copy of `tools/` that merely sits inside some repo. Between them the two hooks cover every `git pull`: a fast-forward or merge fires `post-merge`, a rebase over local commits fires `post-rewrite`. Each hook is a few stable lines calling the *just-pulled* checkout's own `tools/cdd-worktree.sh sync`, so the logic is always the current one. The hook ignores `post-rewrite`'s `amend` trigger (no pull), unsets the `GIT_DIR` git exports to a hook in a linked worktree, skips a checkout whose helper predates `sync`, and always exits 0. | ||
|
|
||
| `sync` acts only when the checked-out branch is the default branch, so a feature worktree's `/cdd-merge-base`, which fires the same shared hook, never installs. The default branch comes from `origin/HEAD` via git alone: `cdd-worktree-default-branch` would resolve the code-host adapter and announce it inside every pull. Then, if any shipped helper or adapter differs (`cmp`) from its installed counterpart — a missing counterpart counts, an extra installed file never does — it re-runs both installs from the checkout with their output suppressed and prints one line; a failed reinstall prints one line naming the two install commands. A hook without the marker line is the user's: `install` leaves it byte-identical and prints the line to add to it. Under `core.hooksPath` it writes nothing and says so. Hooks are replaced by rename, never rewritten in place, because the running hook may be the one whose `sync` is reinstalling. `cdd-worktree-done` pulls the default branch in the main worktree, so finishing a task updates the install too. | ||
|
|
||
| **Self-reload.** Each helper records its absolute path and `cksum` when it is sourced. Every public entry point — `cdd-worktree`, `cdd-worktree-done`, `-list`, `-gc`, `-resume`, and `cdd-state` — first calls `cdd-worktree-reload` (or `cdd-state-reload`): if the file changed on disk, it re-sources it and re-dispatches to the new definition in the same shell, so a `cd` still reaches the caller. Bash finishes the in-flight old body (it executes a copy of the function), and the new sum is recorded before the source, so a reload cannot loop. It is silent. The PATH shims source fresh and never reload; zsh records nothing and never reloads. A shell still holding code from before this mechanism has no reload guard and needs one restart. | ||
|
|
||
| **`update`.** `bash ~/.cdd/tools/cdd-worktree.sh update` serves a machine with no checkout (the curl install), or one that wants the newest now. It makes a shallow, blobless, sparse clone of upstream `main`'s `tools/` into a temp dir (about 1.5 s), compares it with the install as `sync` does, and runs both installs from the clone when anything differs, printing "up to date" or "updated"; an unreachable upstream is one line, exit 1, nothing changed. `CDD_UPSTREAM_URL` overrides the upstream as a test knob. Because `update` overwrites the very script bash is executing, the dual-mode dispatch ends in an explicit `exit`: bash reads a script as it runs it, and after the overwrite it must never read past the dispatch block. `cdd-state`'s unknown-verb and invalid-stage errors (still exit 2) add one line naming `update`, since a project's commands outrunning the installed helper is their usual cause. | ||
|
|
||
| **Known limit.** The command whose own pull brings a fix still runs the old code: `cdd-worktree-done` pulls mid-run and is not re-entrant once it has `cd`'d to the main worktree, so it is not re-executed. The next command runs the new code. | ||
|
|
||
| `scripts/toolchain-sync-assert.sh` (the `toolchain-sync` gate) pins all of this offline, against a bare "upstream", a clone of it as the checkout, and a throwaway `HOME`. The gates that run `install` themselves (`install-smoke`, `adapter-bindings`) install from a copy of `tools/` outside any repo, so a check run never hooks the developer's own checkout. | ||
|
|
Owner
Author
There was a problem hiding this comment.
Pretty long paragraphs. Trim, compress, keep the relevant parts.
Owner
Author
There was a problem hiding this comment.
Addressed in 5e2369d: the section is about half the length now, keeping only the invariants. The mechanics stay in the code comments and the rationale in ADR 0014.
Trim the section to its invariants; mechanics stay in the code comments and the rationale in ADR 0014. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JyhD5C5M5Vsi6k7jzdzEwS
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.
Why
Merging a helper fix (e.g. #125 for #124) changed nothing on a machine until someone re-ran
tools/cdd-worktree.sh install, and long-lived shells kept the old functions even after that.cdd-worktree-donewas run against an already-fixed bug this way.What
Three small pieces, none in the background, none printing anything while the install is current (ADR 0014):
installwrites a managedpost-merge+post-rewritehook into it. A pull that lands on the default branch and leavestools/differing (by content) from~/.cdd/tools/reinstalls both helpers and the adapter library, in one line. Feature-branch merges,commit --amend, and foreign hooks /core.hooksPathare left alone.cdd-worktree*/cdd-stateentry point re-sources its file when it changed on disk and re-dispatches to the new definition, silently.bash ~/.cdd/tools/cdd-worktree.sh updatefor machines without a checkout: sparse clone of upstreammain'stools/, install if different.cdd-state's unknown-verb / invalid-stage errors now name it.The handoff asked for warn-only checks everywhere; that design was replaced at plan approval with this simpler, auto-reinstalling one (recorded in the plan).
Notes
cdd-worktree-done) still runs the old code; the next command is current.install-smokeandadapter-bindingsnow install from a non-git copy oftools/, so CI never hooks the developer's checkout.tools/cdd-worktree.sh installonce from the main CDD checkout to get the hook, and restart open shells once.Tests
New
toolchain-syncgate (offline, stub upstream); all 27 gates pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01JyhD5C5M5Vsi6k7jzdzEwS