Skip to content

Responder lock: a live owner is evicted when pstart carries stray whitespace - #138

Merged
ThinkOffApp merged 3 commits into
mainfrom
fix/responder-lock-pstart-whitespace
Oct 4, 2026
Merged

ThinkOffApp merged 3 commits into
mainfrom
fix/responder-lock-pstart-whitespace

Conversation

@ThinkOffApp

Copy link
Copy Markdown
Owner

The bug

session-bootstrap.sh reads pstart verbatim from the lock file, but compares it against trimmed ps output:

proc_lstart:  ps -p "$1" -o lstart= | sed 's/^ *//;s/ *$//'   # trimmed
read_lock:    sed -n 's/^pstart=//p' "$LOCK_FILE" | head -1   # verbatim

ps -o lstart= emits trailing padding. So a lock file whose pstart keeps that padding compares unequal in owner_alive(), the live owner reads as dead, and the next session deletes the lock and claims it.

Why it matters: the two halves disagree

responder-lock.mjs already trims both sides — readResponderLock does m[1].trim(), procStart does .trim(). So for the same lock file:

verdict on a padded pstart
responder-lock.mjs (enforcement) valid lock, another live session holds it → refuse the post
session-bootstrap.sh (claiming) owner is dead → evict and claim

That is a split brain. The symptom is a session that cannot post and cannot keep the lock: room_post refuses it while the bootstrap hands the lock to whoever starts next.

Seen in practice today — a session was evicted 16 s after claiming, then 12 s on a retry. It looked like two sessions fighting over an unarbitrable lock. It was one malformed pstart.

The fix

Trim LOCK_PSTART exactly as proc_lstart trims, so both halves agree on what a live owner looks like. One line plus the comment explaining why.

Verification

The test asserts a live owner with padded pstart is not evicted, and asserts the fixture is real (assert.match(realStart, / $/)) so it cannot silently stop testing the thing if ps formatting changes.

  • before the fix: fails — "a live owner must not be evicted over whitespace"
  • after the fix: passes
  • full suite: 815/815

🤖 Generated with Claude Code

…espace

session-bootstrap.sh reads pstart verbatim out of the lock file while
proc_lstart() trims the ps output it compares against:

    proc_lstart:  ps -p "$1" -o lstart= | sed 's/^ *//;s/ *$//'
    read_lock:    sed -n 's/^pstart=//p' "$LOCK_FILE" | head -1

So a lock whose pstart keeps ps's trailing padding compares unequal in
owner_alive(), the LIVE owner reads as dead, and the next session removes
the lock and claims it.

responder-lock.mjs already trims both sides (readResponderLock does
m[1].trim(), procStart does .trim()), so the two halves disagreed about the
same file: the MCP path refused the post because it saw a valid lock held by
another live session, while the bootstrap kept handing that lock to whoever
started next. Observed today as a session being evicted 16 s and then 12 s
after claiming, which looked like two sessions fighting and was actually one
malformed pstart.

Trims LOCK_PSTART the same way proc_lstart trims, so the bash and JS halves
agree on what a live owner looks like.

Test fails before the change ("a live owner must not be evicted over
whitespace") and passes after. Full suite 815/815.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-03T21:20:19.761546Z 3237402 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3237402c29

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/session-bootstrap.test.mjs Outdated
const owner = startSleeper();
const realStart = execFileSync('ps', ['-p', String(owner), '-o', 'lstart='], { encoding: 'utf8' })
.replace(/^ +/, '').replace(/\n$/, ''); // leading trimmed, TRAILING KEPT
assert.match(realStart, / $/, 'this fixture needs ps to emit trailing padding');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not require ps to emit trailing padding

On Linux with procps-ng, ps -o lstart= ends immediately before the newline, so this assertion fails before exercising the fix. I reproduced this with procps-ng 4.0.4, and the repository's Node.js CI runs npm test on ubuntu-latest, meaning every matrix job can fail here. Construct the padded fixture explicitly from the trimmed start time instead of depending on platform-specific ps formatting.

Useful? React with 👍 / 👎.

The fixture built the padded pstart from ps output and asserted ps had
emitted trailing spaces. macOS pads lstart and Linux does not, so CI on
ubuntu failed the fixture guard itself: 'this fixture needs ps to emit
trailing padding'.

The guard was right to refuse - it will not pass while testing nothing -
but it was guarding the wrong premise. The defect is that read_lock takes
pstart verbatim from the FILE, so what must be tolerated is padding in the
file, whichever tool wrote it. Padding both ends explicitly tests that on
every platform.

Still fails without the trim and passes with it; full suite 815/815.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ThinkOffApp
ThinkOffApp merged commit 4921167 into main Oct 4, 2026
2 checks passed
@ThinkOffApp
ThinkOffApp deleted the fix/responder-lock-pstart-whitespace branch October 4, 2026 11:21
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