Repository navigation
Description
Investigation Summary
cli-test-shards (4)on PR fix(onboard): rebuild on messaging credential drift #7853 failed attest/mcp-lifecycle-lock.test.ts:436because a corrupt lock disappeared after the acquisition had already timed out.- Current
mainchecks the timeout only at the start of each acquisition-loop iteration. - An asynchronous lock observation can start before the deadline, resume after both the deadline and corrupt-lock grace, then reap the existing lock before the next timeout check.
- The same gap can publish a new lock before the deadline but enter the critical section after it.
- No open issue or PR matching this deadline contract was found.
Description
MCP lifecycle-lock acquisition can still mutate lock state after its timeout budget has expired.
The acquisition loop checks elapsed time at the beginning of an iteration. Filesystem reads, links, and identity observations then run asynchronously. If one of those operations is delayed, the iteration can cross the deadline and still:
- create a stale-lock reaper and delete an existing corrupt lock, or
- publish a new lock and enter the protected operation.
The timeout should be a fail-closed boundary: after the deadline, acquisition must not remove a pre-existing lock or enter the critical section. If the caller's own lock generation was published just before the deadline was detected, NemoClaw should release only that generation and return the existing timeout error.
Observed CI evidence:
- PR: fix(onboard): rebuild on messaging credential drift #7853
- Exact head:
8d144240adabbbe36901a32dd633af8312203a27 - Failed job:
cli-test-shards (4)/ job90742116177 - Failure:
ENOENTreading the corrupt lock immediately after the expected timeout rejection
Reproduction Steps
- Create a malformed MCP lifecycle lock for a sandbox.
- Start acquisition with a timeout shorter than the corrupt-lock grace period.
- Delay an asynchronous observation after the iteration's timeout check until the monotonic clock is beyond both the acquisition deadline and corrupt-lock grace.
- Observe that current
maincan classify and reap the lock before the next loop-level timeout check. - Repeat with an absent lock and delay deadline observation until after the atomic link; current
maincan return the acquired lock and run the callback after the deadline.
A deterministic regression test can inject a monotonic clock to model both boundaries without relying on runner scheduling.
Environment
- OS observed in CI: Ubuntu 24.04.4, x86_64
- Node.js: repository CI version
- NemoClaw:
mainatd52d4599a18490e7f8efc6e8062296fffcbea4a7 - Affected subsystem: host-side MCP lifecycle lock acquisition
Debug Output
Error: ENOENT: no such file or directory, open '.../mcp-lifecycle-locks/8ed3f6ad...lock'
at test/mcp-lifecycle-lock.test.ts:436:15
Checklist
- I confirmed this bug with exact-head CI evidence and a deterministic clock-controlled regression.
- I searched existing issues and pull requests and found no duplicate.
Activity
wscurran commentedon Jul 31, 2026
✨ Thanks for the detailed investigation summary. This describes a clear race condition in the MCP lifecycle-lock acquisition loop where async operations can cross the deadline and still mutate lock state.
Related open PRs:
fix(state): make lifecycle lock timeouts side-effect free (#7859)