provenance (pin half): a dev-machine run executes skill text that can change underneath it #56

Open
opened 2026-08-24 09:24:28 +00:00 by jbr870 · 1 comment
Owner

Observation

Split out of #34 when its record half was promoted into a feature. #34 opened with two
independently useful halves — record (a run's Phase Outcome carries the suite version/SHA it
executed) and pin (a run resolves its skill text once, so the text cannot move underneath it).
The record half is now in flight. This issue carries what is left, narrowed by what #50 actually
delivered
rather than restating #34's original framing.

#34's original evidence: ~/.claude/skills/_shared on the desktop was a symlink into a live git
work tree that received 20+ commits, including a merge, during a single run — one mid-QA commit
changed the QA playbook's preconditions between stage launches. The model noticed once and re-read
one file.

What #50 changed, and what it did not

The plugin migration solved this for consumers, and only for consumers:

consumer machine dev machine (this box)
plugin root ~/.claude/plugins/cache/devwork-skills/dev/<VERSION>/ ~/.claude/skills/dev~/Coding/devwork-skills/plugin
shape an extracted, immutable copy per version — not a git work tree a live checkout
can it move mid-run? no — a new version is a new directory yes — every commit, rebase or branch switch changes it under any running session

So the residual is precisely: a run on the machine where the suite is being developed executes
skill text that can change underneath it, and nothing prevents or even detects that.
This is not
a hypothetical — it is where #34's original observation was measured, and the dev box is exactly
where the suite is dogfooded most heavily.

Why it is not simply "solved by installing the plugin here too"

Installing the released plugin on the dev box would pin it, but at the cost of the thing that makes
this box useful: changes must be exercised before they are released. The dev box deliberately
runs unreleased text. Any fix has to preserve "run what I just edited" while removing "and it may
change again halfway through."

Recurrence

Mechanical and permanent on the dev box; every multi-session or long-running day has this property.
Its consequence is now bounded to the dev machine, which is why this is filed at lower priority
than #34's record half — but note that the record half does not fix it, it only makes it
visible after the fact: a stamped SHA tells you the text moved, it does not stop it moving.

Hypothesis

Not yet designed. Candidate directions, cheapest first:

  1. Detect, don't prevent — cheap and possibly sufficient once #34's record half lands: stamp the
    SHA at run start, re-probe at phase boundaries, and raise a loud finding if it changed. Turns a
    silent confound into a visible one.
  2. Per-run resolved copy — a run snapshots the plugin tree (or a worktree at the start SHA) and
    executes from that. Preserves "run what I just edited" as of run start; costs a copy per run and
    a resolution step every skill would have to honour.
  3. Opt-in pin — an operator-facing switch that freezes a run to a SHA, defaulting off, for the
    cases that matter (a QA sweep, a comparison across slots).

Direction 1 is a natural follow-on to #34 and may make 2/3 unnecessary; sequence accordingly.

Suggested lane

Full pipeline if it becomes a per-run resolution mechanism (2 or 3); possibly a small amendment
on top of #34's helper if it lands as detection (1). Decide after #34's record half ships.

Split out of #34 on 2026-08-24 during the fan-in that promoted #34's record half. #22, which #34
originally named as the home for pinning, is closed — superseded by #50.

## Observation Split out of **#34** when its *record* half was promoted into a feature. #34 opened with two independently useful halves — **record** (a run's Phase Outcome carries the suite version/SHA it executed) and **pin** (a run resolves its skill text once, so the text cannot move underneath it). The record half is now in flight. This issue carries what is left, **narrowed by what #50 actually delivered** rather than restating #34's original framing. #34's original evidence: `~/.claude/skills/_shared` on the desktop was a symlink into a live git work tree that received **20+ commits, including a merge, during a single run** — one mid-QA commit changed the QA playbook's preconditions between stage launches. The model noticed once and re-read one file. ## What #50 changed, and what it did not The plugin migration solved this **for consumers, and only for consumers**: | | consumer machine | dev machine (this box) | |---|---|---| | plugin root | `~/.claude/plugins/cache/devwork-skills/dev/<VERSION>/` | `~/.claude/skills/dev` → `~/Coding/devwork-skills/plugin` | | shape | an **extracted, immutable copy** per version — not a git work tree | a **live checkout** | | can it move mid-run? | **no** — a new version is a new directory | **yes** — every commit, rebase or branch switch changes it under any running session | So the residual is precisely: **a run on the machine where the suite is being developed executes skill text that can change underneath it, and nothing prevents or even detects that.** This is not a hypothetical — it is where #34's original observation was measured, and the dev box is exactly where the suite is dogfooded most heavily. ## Why it is not simply "solved by installing the plugin here too" Installing the released plugin on the dev box would pin it, but at the cost of the thing that makes this box useful: changes must be exercised **before** they are released. The dev box deliberately runs unreleased text. Any fix has to preserve "run what I just edited" while removing "and it may change again halfway through." ## Recurrence Mechanical and permanent on the dev box; every multi-session or long-running day has this property. Its *consequence* is now bounded to the dev machine, which is why this is filed at lower priority than #34's record half — but note that the record half does not fix it, it only makes it *visible after the fact*: a stamped SHA tells you the text moved, it does not stop it moving. ## Hypothesis Not yet designed. Candidate directions, cheapest first: 1. **Detect, don't prevent** — cheap and possibly sufficient once #34's record half lands: stamp the SHA at run start, re-probe at phase boundaries, and raise a loud finding if it changed. Turns a silent confound into a visible one. 2. **Per-run resolved copy** — a run snapshots the plugin tree (or a worktree at the start SHA) and executes from that. Preserves "run what I just edited" as of run start; costs a copy per run and a resolution step every skill would have to honour. 3. **Opt-in pin** — an operator-facing switch that freezes a run to a SHA, defaulting off, for the cases that matter (a QA sweep, a comparison across slots). Direction 1 is a natural follow-on to #34 and may make 2/3 unnecessary; sequence accordingly. ## Suggested lane **Full pipeline** if it becomes a per-run resolution mechanism (2 or 3); possibly a small amendment on top of #34's helper if it lands as detection (1). Decide after #34's record half ships. *Split out of #34 on 2026-08-24 during the fan-in that promoted #34's record half. #22, which #34 originally named as the home for pinning, is closed — superseded by #50.*
Author
Owner

Live dogfood evidence (2026-08-25, parallel wave 1): issue #61's requirements run recorded
suite: {"source":"git","sha":"07d9ad8","dirty":true} on PO-61-1 — while the session had been
launched when main stood at 7453ac5. Two distinct signals in that one stamp:

  1. The real #56 hazard, observed live: the executing plugin (main checkout, via the
    ~/.claude/skills/dev symlink) advanced 7453ac507d9ad8 during the run — the parallel meta
    session merged six amendments while two feature sessions executed skill text from that moving
    checkout. The #57 provenance stamp made this visible per record (which is its job — the record
    half), but nothing pinned what a given phase actually executed across its whole duration
    (the pin half — this issue).
  2. A false-dirty, now fixed: dirty:true was caused by the untracked harness-managed
    .claude/worktrees/ directory (where parallel-session worktrees live), not by edits to
    shipped files. Fixed by gitignoring the path (commit cda2366), so future stamps report dirty
    only when tracked content actually diverges. Worth remembering at design time: the dirty flag
    is repo-wide — anything untracked inside the checkout poisons it, so what the flag should
    mean (shipped-tree divergence vs any divergence) is a pin-half design question.
**Live dogfood evidence (2026-08-25, parallel wave 1):** issue #61's requirements run recorded `suite: {"source":"git","sha":"07d9ad8","dirty":true}` on PO-61-1 — while the session had been *launched* when main stood at 7453ac5. Two distinct signals in that one stamp: 1. **The real #56 hazard, observed live:** the executing plugin (main checkout, via the `~/.claude/skills/dev` symlink) advanced 7453ac5 → 07d9ad8 *during* the run — the parallel meta session merged six amendments while two feature sessions executed skill text from that moving checkout. The #57 provenance stamp made this visible per record (which is its job — the record half), but nothing pinned what a given phase actually executed across its whole duration (the pin half — this issue). 2. **A false-dirty, now fixed:** `dirty:true` was caused by the untracked harness-managed `.claude/worktrees/` directory (where parallel-session worktrees live), not by edits to shipped files. Fixed by gitignoring the path (commit cda2366), so future stamps report dirty only when tracked content actually diverges. Worth remembering at design time: the dirty flag is repo-wide — anything untracked inside the checkout poisons it, so what the flag should *mean* (shipped-tree divergence vs any divergence) is a pin-half design question.
Sign in to join this conversation.
No description provided.