Skip to content

TDD 0069: session-owned /build-tdds lock and 0067 follow-ups - #190

Merged
cahenesy merged 4 commits into
masterfrom
build/20261006-131111/0069-build-tdds-hardening
Oct 6, 2026
Merged

cahenesy merged 4 commits into
masterfrom
build/20261006-131111/0069-build-tdds-hardening

Conversation

@cahenesy

@cahenesy cahenesy commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Implements TDD 0069 (PRD FR-18, FR-43, FR-88, FR-41). This PR hardens /build-tdds (3.49.0) and adds the 0067 follow-ups. Plugin 3.50.0 → 3.51.0.

Stacked on #189 (TDD 0068). Merge #189 first, with auto-delete of head branches on, so this PR retargets to master.

What changed

Session-owned run lock (scripts/lib/run-record.sh, FR-18 / FR-43):

  • tl_session_pid walks the ps ancestors past shells and wrappers (bash, bwrap, timeout, …). It never returns PID 1, and it prints <pid> <start>. The lock is now owned by the long-lived session, not by the dead-on-return Bash-tool shell $$, so the FR-18 guard actually guards.
  • The lock line is pid=<pid> start=<lstart>. The owner counts as live only while that PID still has the same start time (detects PID reuse). Legacy bare-PID locks are still read.
  • Acquisition uses bash noclobber, not mkdir, which is not atomic on uutils. It is re-entrant for the same owner and refuses an unreadable or partial line.
  • A dead or reused owner is reclaimed under a noclobber lock.reclaim guard, which re-reads the lock before deleting it. A stale guard is broken with an atomic mv.
  • tl_run_unlock removes only its own lock or a dead owner's. tl_run_lock_reclaim remains as an alias.
  • SKILL.md gains the tl:lock / tl:unlock blocks.

0067 follow-ups (FR-88 / FR-41):

  • (a) The fall-back reason travels by file through tl:escalation-fellback-record. It is never interpolated into a shell, its control bytes are stripped, and only the first line is kept.
  • (b) The step-7 rate-limit rule points to the credits_required fall-back. That refusal is not a pause.
  • (c) tests/live/escalation-probe.sh removes its harness project folder on exit. It is strictly guarded and never follows a symlink.
  • (d) tl_run_retry_begin copies the failed report itself and prints report= / implementer_report= / archive=. tl:retry-begin is a single call.
  • (e) tl_escalation_fellback_check and tl:escalation-verify validate the base sha (40-hex), so a bad value gives rc 2.

Gates

Gate Result
test-first PASS: test(failing) 64fe70c precedes feat f2491f8
ci-checks PASS
runtime-verify PASS: all 14 observation points, driven live. 27 evals, 0 failures: build-hardening 62/0, escalation 305/0, run-record 26/0
review PASS, security checklist applied, no blocking findings

Deviations (accepted in review)

  • tl_run_unlock takes an optional owner argument, mirroring tl_run_lock.
  • Lock and unlock stop after 20 loop iterations ("held by a concurrent run").
  • A malformed explicit owner gives rc 2. tl:unlock prints lock=released.
  • The reason block also validates TL_REPO / TL_RUN / TL_SLUG and never reads a symlinked reason file.
  • The stale-guard breaker checks again for a fresh guard after its mv.

Non-blocking review notes (follow-up candidates)

  • On macOS, ps -o comm= can print a full path (/bin/zsh), and the stop-list comparison would then miss the shell. The fix is to strip the path and a leading - (run-record.sh:168).
  • When two processes break a stale guard at the same moment, one can leave a stray .stale file (:238).
  • The "lock unreadable; held" message gives neither the lock path nor a recovery hint (:273).
  • The reason line does not strip UTF-8 C1 control characters.

Not verified here

  • The live (non-stub) claude escalation probe.
  • The process tree on the Grok harness. If tl_session_pid returns rc 1 there, the skill stops loudly.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NpGG21Y2VGGhTQaFr4oshB

cahenesy and others added 4 commits October 6, 2026 09:49
New tests/build-hardening.test.sh drives TDD 0069's verification plan
(obs 1-13 incl. 5b): tl_session_pid under real python3 parents and
wrapper chains, the session-owned lock with sleeper owners, concurrent
racers on temp repos, the extracted tl:lock / tl:unlock /
tl:escalation-fellback-record blocks, the probe's folder cleanup against
a stub claude, tl_run_retry_begin's report copy, and base-sha
validation. escalation.test.sh [12] and run-record.test.sh [E] move to
the new retry-begin shape and lock-line format; the aggregator registers
the new eval.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq
run-record.sh: tl_session_pid walks ps ancestors past shells and
wrappers (never PID 1) and prints `<pid> <start>`. tl_run_lock records
`pid=<pid> start=<start>`, treats an owner as live only with the same
lstart, acquires with bash noclobber (not mkdir), re-enters for the same
owner, refuses an unreadable line, and reclaims a dead or reused owner
under a noclobber `lock.reclaim` guard with a re-read compare (stale
guard broken by an atomic mv). tl_run_unlock removes only its own or a
dead owner's lock; tl_run_lock_reclaim is an alias. tl_run_retry_begin
copies the failed report itself and prints report= / implementer_report=
/ archive=. tl_escalation_fellback_check validates the base sha.

SKILL.md: tl:lock / tl:unlock blocks; the fall-back reason travels by
file through tl:escalation-fellback-record; step 7's rate-limit rule
points to the credits_required fall-back; tl:retry-begin is one call;
tl:escalation-verify validates TL_BASE_SHA.

escalation-probe.sh: the EXIT trap removes the harness project folder
for its mktemp scratch dir, with strict guards (never a symlink).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq
Base automatically changed from build/20261006-131111/0068-format-and-lint-honors-config to master October 6, 2026 20:08
@cahenesy
cahenesy merged commit b782b72 into master Oct 6, 2026
@cahenesy
cahenesy deleted the build/20261006-131111/0069-build-tdds-hardening branch October 6, 2026 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant