Skip to content

fix(exec): follow-ups from independent review of #304/#306/#290 - #324

Closed
MendixMau wants to merge 4 commits into
masterfrom
fix/exec-followups
Closed

MendixMau wants to merge 4 commits into
masterfrom
fix/exec-followups

Conversation

@MendixMau

Copy link
Copy Markdown
Owner

Five defects in project-bin/exec.sh that independent reviews of the merged PRs #304, #306 and #290 found. Each fix comes with fixture cases that fail on master.

1. Stale-lock takeover allowed more than one holder (#304, medium)

Problem. To take over a stale lock, a contender moved it aside. If the file it moved turned out to be a fresh lock, it put it back with mv -f. That is check-then-act. With 3 or more contenders, B moved A's fresh lock aside, C's noclobber write landed in the gap, and B then saw a lock and deleted A's.
Fix. Takeover now goes through a second O_EXCL file, .exec.lock.takeover. While holding it, a contender re-reads the lock and removes it only if it still holds exactly the stale line it read earlier. The fresh lock is never moved aside, so there is nothing to restore.

  • A takeover file whose pid is dead, or which is older than 10 s, is stale. An empty one is stale once it is a minute old.
  • A stale takeover file is moved aside. If what was moved is not the file that was judged stale, it is put back with ln, which never clobbers.
  • When the takeover file is busy, the contender sleeps 1 s and looks again; there are now 6 attempts.
  • Plain bash 3.2, noclobber, mv and ln; no flock.

Evidence. The new case in tests/wave2/test-exec-lock-watch.sh runs 10 rounds of 12 contenders started behind a barrier against a dead-pid lock. A probe in the snapshot stub counts how many runs are inside the locked section at once.

  • On master, 2 to 3 of 10 rounds had 2 or 3 holders at once (two separate runs).
  • With the fix, 0 of 10 in three runs.

Further cases: a dead contender's takeover file does not block, and an old empty takeover file does not block (CLAUDE.md instrument rule 7).

2. --watch-compat overclaimed "watch can apply this" (#304, low)

Problem. Any statement the classifier did not recognise was reported as watch-safe.
Fix. Only the statements on skills/ui-loop.md's watch-safe list now say "watch can apply this": ALTER PAGE, ALTER SNIPPET, CREATE [OR …] SNIPPET, and CREATE OR REPLACE|MODIFY PAGE. Read-only statements are neutral. Every other statement prints unknown — restart if behaviour looks stale: <line N: head>, both in the advisory mode and after a real run.

This changes two outputs:

  • ALTER ENTITY … MODIFY (no ADD) is now unknown.
  • CREATE MODULE alone is now unknown.

Golden input:

  • create association taken verbatim from a pending bug issue.
  • CREATE ENUMERATION from skills/build/workflow-example.mdl.
  • create java action with a $$ body, verbatim from the bug87 issue.
  • The captured DESCRIBE NAVIGATION fixture.
  • The repo has no captured ALTER ENTITY … MODIFY ATTRIBUTE or CREATE CONSTANT statement. Those two are written from the grammar, and the fixture says so.

3. Two-tree coverage (#304, low)

New case: a checkout with the .mpr under app/. It checks that:

  • the lock is taken at the project root;
  • a live holder refuses a second run;
  • a SIGKILLed holder's lock is taken over;
  • a dirty app/ model is refused;
  • --commit-first commits exactly app/mprcontents/* and leaves an unrelated staged file staged.

These cases pass on master too. They add coverage for CLAUDE.md field-proof rule 2 rather than proving a bug.

4. --cr after --commit-first (#306)

Problem. --cr was only read as the first argument. In the documented order, --commit-first --cr CR-07 x.mdl, the model was committed and then "--cr" was used as the script path.
Fix. --cr, --commit-first and then --patch or an advisory mode are now parsed in one loop, in any order. An unknown flag, or a script name starting with --, exits 2 before the lock, the commit or the snapshot.
Fixture. The documented order runs, and its BUILD-LOG row carries cr: CR-07. --commit-first --bogus x.mdl is refused with no commit, no snapshot and no exec.

5. ui-only test lane text (#290)

"no other module's journeys" is now "re-run the journeys of every page the change reaches (embedders of a changed snippet/layout)". A grep case for this is in test-exec-change-class.sh.

Checks

Fixtures:

  • test-exec-lock-watch.sh: 77/0. On master it fails 14 cases (the race, the takeover file, flag order, misplaced flag, 6 watch-compat cases).
  • test-exec-change-class.sh: 21/0 (1 failure on master).
  • test-bug07-08.sh project-bin/exec.sh: 43/0.
  • test-metrics-ledger.sh: 42/0.
  • test-change-log.sh bin/gate-check.sh: 39/0. One run showed 1 failure while two heavy fixture runs were going on in parallel; the rerun passed.
  • test-lint-gate-accept.sh: 20/0.
  • test-exec-approval.sh: 43/0.

Repo checks:

  • check-scripts, check-portability and check-no-private-citations are clean.
  • The leak guard is clean (run without a denylist).
  • check-pr-discipline origin/master is clean.
  • The forbidden-word grep of the added lines has no hits.

Not covered / follow-ups

  • Field run still owed. These fixes have only run against fixtures, never on a real project.
  • project-bin/test-scratch.sh has the same bug. It takes the same lock with a blind mv -f takeover (lines 81–82), so it is not mutually exclusive with exec.sh either. It should use the same .exec.lock.takeover protocol. That file is outside this PR's scope (another change owns it).
  • skills/ui-loop.md needs a one-line update. Lines ~208–212 list two verdicts; they should mention the third (unknown — restart if behaviour looks stale). Not edited here, to stay in scope.
  • Not tested on macOS or Git Bash. The constructs used are portable (noclobber, mv, ln, find -mmin, sleep 0.01 in the fixture only).

Refs #287 #289 #274

🤖 Generated with Claude Code

https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw


Generated by Claude Code

- Stale-lock takeover serialised through an O_EXCL .exec.lock.takeover file;
  the lock is removed only if it still holds the stale line read (no mv -f
  restore). 12 contenders x 10 rounds: master 2/10 rounds with 2-3 holders,
  fixed 0/10.
- --watch-compat: only ui-loop.md's watch-safe list says "watch can apply
  this"; other statements print "unknown — restart if behaviour looks stale".
- Two-tree (.mpr under app/) cases for the lock and --commit-first.
- Leading flags parsed in one loop (--commit-first --cr CR-NN works); a
  script name starting with -- is refused before any commit or snapshot.
- ui-only test lane names the embedders of a changed snippet/layout.

Refs #287 #289 #274

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
The watch paragraph listed only `restart needed` and `watch can apply
this`; exec.sh now also prints `unknown — restart if behaviour looks
stale` for statements on neither column. Reworded within the same
paragraph to stay inside the baseline word budget.

Refs #287 #289

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau pushed a commit that referenced this pull request Oct 13, 2026
…tocol (#324)

test-scratch.sh shares .mpr-snapshots/.exec.lock with exec.sh, so its stale-lock
takeover now mirrors exec_lock_takeover from PR #324 (fix/exec-followups @ ed4efbb):
the same O_EXCL <lock>.takeover file, stale when its pid is dead or after 10 s (empty:
after a minute), re-read and remove only the exact stale line, put a wrongly moved
takeover file back with ln. The port lock uses the same protocol.

New cross-script fixture tests/wave2/test-shared-lock-takeover.sh: 4 rounds of 5 exec.sh
+ 5 test-scratch contenders on one dead-holder lock never produce two holders (master's
test-scratch: overlaps in 3 of 3 runs). exec.sh's block comes from the real exec.sh once
it carries exec_lock_takeover, else from a verbatim copy of #324's block.

Refs #244

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau pushed a commit that referenced this pull request Oct 13, 2026
…ith #324's review)

`find -mmin +0` treats a fresh empty takeover file as stale at once (GNU matches 0-2 s
old files, BSD rounds up), so a takeover in progress could be broken into: two holders
under load. Now -mmin +1 (empty for over a minute). The takeover file is removed only
when its content is what was judged stale (a moved-aside live one is put back with ln,
and kept if that fails), and a finished takeover removes the file only while it still
holds its own line. Fixture: a fresh empty takeover file blocks the takeover and is left
alone; one over a minute old (touch -t 2000) is stale. Fails on the previous body.

Keep in step with exec.sh's exec_lock_takeover (PR #324).

Refs #244

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…ith test-scratch

Review of #324: `find -mmin +0` matched a takeover file created a second
ago, so a live contender's fresh empty file was moved aside and a third
contender got in (reviewer: 2-3 holders in 6/15 and 16/30 rounds with a
100 ms delay). Now -mmin +1; a moved-aside copy is deleted only if it is
what was judged stale, else restored with ln (left on disk if ln fails);
the holder removes the takeover file only while it holds its own line.
Body identical to test-scratch.sh take_over() in #322. Reviewer's delayed
harness against this body: 0/15 rounds with >1 holder.

Also: microflow-body verbs, CREATE FOLDER and MOVE ... TO FOLDER are
neutral for the watch verdict; agent-roles.md names the unknown verdict.

Refs #287 #289

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau pushed a commit that referenced this pull request Oct 13, 2026
The aligned exec_lock_takeover (-mmin +1, own-file-only removal) now matches
test-scratch take_over() apart from signature and parameter names (diffed).

Refs #244

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
PR #312 makes lint-gate.sh print a non-blocking `note: SEC014 not
configured` on stderr. run_lint_gate captured that output and showed it
only on failure, so the note never appeared on a clean exec. Always echo
that one line; exit code unchanged. Fixture: stub lint-gate with the
note on stderr and rc 0 -> exec output shows it (fails on the prior head).

Refs #287

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau pushed a commit that referenced this pull request Oct 13, 2026
…al, shared stale-lock takeover, SEC014 note echo

Conflict: skills/agent-roles.md — the Dispatching section moved to
skills/dispatching.md in #320; kept integration's agent-roles.md and
re-applied #324's restart-paragraph wording in dispatching.md.

Review: independent joint review (agent a1e0b45) of head 90a6957 — MERGE.
Contention harness 0 double holders on dead lock; follow-up owed for a
takeover race after a mid-takeover crash (2/160 rounds; re-check own line
before compare-and-rm). Fixtures: exec-lock-watch 84/0, exec-change-class
21/0, bug07-08 43/0, shared-lock-takeover 6/0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau added a commit that referenced this pull request Oct 14, 2026
)

Every constituent PR was reviewed by a fresh agent that did not write it (CLAUDE.md "Merging a PR"); only MERGE verdicts joined. CI green on each constituent head and on this head (40814fc); full suites run on the branch.

Review lines (per merged PR):
- #315 context trim — MERGE; 360/385 removed lines found verbatim in their new homes, rest read by hand; baseline 79,997 -> 76,259.
- #316 independent-review + word-budget rules — MERGE-AFTER-FIX (3 rounds), fixes applied as specified.
- #318 page-fidelity displayed text — MERGE after fix round + confirmation.
- #317 interaction-model skill (#291 part 1) — MERGE after fix round + confirmation; no client names leaked.
- #321 PROOF-OF-LOOK per section; final-look not due before derived Stage 6 — MERGE after fix round + confirmation.
- #307 graph-sweep guard; per-BRD conformance ledgers — MERGE after two fix rounds + confirmation.
- #325 extraction-quality section; token-burn fixes — MERGE after fix round + confirmation.
- #310 e2e OQL by alias, negative-span liveness, APP-DOWN, #302 credential regression fix — MERGE after fix round + 3 confirmations.
- #323 inbox promotions — MERGE after fix round + confirmation.
- #308 lint gate BLIND on reverted stock rules — MERGE (FIRST-SEEN reverted) after two fix rounds + confirmation.
- #328 pipefail + grep -q flake — MERGE; 0/30 flakes vs 15/100 on master. Follow-up: run-all resolves bin/lib/ subjects (reviewed, MERGE).
- #319 test-stack-up command-line ownership proof — MERGE after fix round + 2 confirmations.
- #327 change-intake core + on-demand detail; CRLF-safe change log — MERGE after fix round + confirmation.
- #320 follow-ups to #290/#292; Dispatching split out — MERGE after fix round + confirmation.
- #326 status delivery rounds (existing-app mode) — MERGE after fix round + confirmation.
- #312 lint batch 4: SEC014, toolkit CONV009 — MERGE after review + 2 fix rounds + confirmation; field run on mxcli 0.26.
- #324 exec.sh change classes, shared stale-lock takeover, SEC014 note echo — MERGE (joint review); follow-up owed for a takeover race after a mid-takeover crash.
- #322 MxTest after-startup guard, mxcli-run pid check — MERGE (joint review).
- #314 doctor property-set exit code; sync announces its bound — MERGE (joint review).
- #330 review follow-ups (exit 4/5 docs, pipefail, NUL strip) — MERGE-AFTER-FIX, fixes applied; takeover re-check dropped.

Field runs are still owed for most instruments in this batch (stated in each PR).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
@MendixMau

Copy link
Copy Markdown
Owner Author

Landed on master as part of the integration squash #329 (commit 31e90ae), after independent review — its Review: line is in that commit message. Closing as superseded.


Generated by Claude Code

@MendixMau MendixMau closed this Oct 14, 2026
@MendixMau
MendixMau deleted the fix/exec-followups branch October 15, 2026 10:46
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.

2 participants