Builders in linked worktrees of one git store (anton-go: TT-sales,
TT-marketing, V2-D2S0, ...) were halted with E4 against another worktree's
HEAD (75a4cfb, V2-D2S0), and two merged that unrelated branch to satisfy the
gate (e799c02, 7e48b06).
Cause: get_or_set_session_baseline keyed its git-ref layer by session only
(refs/hardtruth/baseline/<conv_hash>). Refs live in the git common dir shared
by every linked worktree, and is_valid_commit only checks the shared object
store, so a ref or cached SHA recorded in one worktree was returned as
another's baseline. Hook payloads without a session id normalise to
"unknown", so all such sessions shared one ref and one cache file
(~/.hardtruth/halts/baseline_unknown_*.json held TT-marketing's 7e48b06).
- Baseline ref is now per (session, worktree realpath): baseline_ref_name,
used by get_or_set_session_baseline and check_manifest_tampering. The
legacy shared name is never read.
- The session cache file's single baseline is used only for the tree its
workspace_path names (hook and tier2_runner).
- No baseline is recorded or read for an id-less ("unknown"/"default")
session.
- E4 messages tell the agent not to merge, rebase or cherry-pick other
branches to satisfy the check, and to report a wrong baseline.
Also fixes the PR #3 session-start snapshot, which recorded 0 repos in all
67 production SessionStarts since install: the daemon resolves each new key
by scanning its 113k-line ledger (~0.9s, serialized), so 2 registrations per
repo never fit the budget. The snapshot is now a manifest {realpath: head,
stash} in the halt dir whose sha256 is registered in ONE daemon call; Stop
trusts it only if the digest matches. Linked worktrees of every discovered
repo are included with their own HEAD. Live: 94 trees in 5.2s, TT-sales and
V2-D2S0 each at their own HEAD.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem 1: worktrees held to another worktree's HEAD
Builders in linked worktrees of one git store (
/Users/ai/vibehard-build/anton-go: TT-sales, TT-marketing, V2-D2S0, …) were halted with E4:75a4cfbwas V2-D2S0's HEAD. Two builders then merged that unrelated branch into their own to satisfy the gate (e799c02,7e48b06).Cause
This is pre-existing code, not PR #3.
get_or_set_session_baselinekeyed its git-ref layer by session only:refs/hardtruth/baseline/<conv_hash>.is_valid_commitonly checks the shared object store, so a ref recorded in one worktree, or a SHA from the session-keyed cache file, was accepted as another worktree's baseline.session_idnormalise to"unknown", so all such invocations shared one ref and one cache file.~/.hardtruth/halts/baseline_unknown_b23a6a84….jsonholds TT-marketing's7e48b06. The store has 29 baseline refs, including two at75a4cfb.Fix
baseline_ref_name(conv, realpath(toplevel))is used by bothget_or_set_session_baselineandcheck_manifest_tampering. The legacy shared name is never read.baseline_shais used only for the tree itsworkspace_pathnames, in both the hook and tier2_runner.unknown/default) session. Sharing one is worse than having none.Problem 2: the PR #3 session-start snapshot never recorded anything
All 67 production SessionStarts since the PR #3 install have
repos_snapshotted: 0. Every edit outside the workspace therefore failed closed. That is safe, but unusable.Cause
Fix
{realpath: {head, stash}}, written to the halt dir. Its sha256 is registered in one daemon call (first write wins).Live check of the real
session_startcommand against the real daemon: 94 working trees recorded in 5.2s, within the 10s hook timeout. TT-sales →5cc39c6and V2-D2S0 →e0986d8, each its own HEAD, matchinggit worktree list.Tests
tests/test_tier2_out_of_workspace_roots.pynow has 42 tests (12 new).New
TestLinkedWorktreeBaselines: one store with two linked worktrees on different branches.unknownsession gets no baseline and no refs.pytest.initamper.Snapshot tests:
Run against
main's code: 11 of the 12 targeted tests fail, including the exact reported E4. The 12th, daemon-down, already fails closed onmain.Full suite:
maina77df23): 223 passed, with the 3 known failures plus thetest_concurrency_loadload flake;Notes
session_id? It was invoked without one for TT-marketing. Since no baseline is now recorded under"unknown", Phase 5 lineage is skipped for such calls, but the ledger premise is still shared across them. That caller should pass a session id.e799c02,7e48b06) remain on those branches. Whether to revert them is for the coordinator to decide.probe-ht-*conversation keys to the live daemon. They are harmless and namespaced.__session_baseline__records on load would fix it, but needs a daemon rebuild.🤖 Generated with Claude Code