ENG-26: webhook accepts both sthr_ and sesn_ session-id prefixes#16
Merged
WillTaylor22 merged 1 commit intoMay 26, 2026
Conversation
ENG-26 — the Anthropic SDK switched session-id prefixes from sthr_ to sesn_; the webhook regex still required sthr_, so every well-formed marker the agent writes returns null from extractSessionId() and the session-resume path silently fell through to fresh sessions. Fix: - app/api/github-webhook/route.ts:20 — regex now matches (?:sthr_|sesn_)[A-Za-z0-9]+. Keeps sthr_ for back-compat with any pre-switch markers still floating in old PRs. - .claude/memory/conventions/pr-session-id-marker.md — example now shows sesn_xxxxxxxxxxxxxxxx and explicitly notes both prefixes work. - .claude/memory/MEMORY.md — one-line hook updated. Verification: build + lint both green. Webhook payload is HMAC-signed so no easy local repro; regex change is small enough to verify by eye.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Owner
Author
|
AGENT_REVIEW: APPROVED — One-line regex change correctly extends the session-id marker match to accept both What I checked
Notes (non-blocking, FYI to the manager, not asks for this PR)
|
Owner
Author
|
Closing as superseded. PR #15 landed the identical ENG-26 fix (regex now accepts both ENG-26 is Done. No code lost. |
WillTaylor22
added a commit
that referenced
this pull request
May 26, 2026
Add convention `check-open-pr-before-ticket-pickup.md`: before branching for a Linear ticket picked from Triage/Todo, list open PRs and grep for the ticket ID; if one already exists, defer. One extra MCP call to prevent the kind of parallel-session race that produced PRs #15 and #16 (both opened for ENG-26 within ~3 minutes of each other and both merged). Standalone memory PR — no Linear ticket.
This was referenced May 26, 2026
WillTaylor22
added a commit
that referenced
this pull request
May 26, 2026
Captures the PR #18 vs PR #20 ENG-25 race — same shape as the PR #15 vs #16 ENG-26 race that prompted the original check-open-pr-before-ticket-pickup convention. Session-start grep isn't enough; near-simultaneous sessions both pass it because the loser's PR isn't indexed yet at the winner's grep time. Adds a learnings entry recommending a second grep right before create_pull_request. Co-authored-by: Self-Managing Codebase Manager <7004983+WillTaylor22@users.noreply.github.com>
WillTaylor22
added a commit
that referenced
this pull request
May 26, 2026
…#20) * ENG-25: webhook accepts plain-text session-id marker (MCP-survivable) The convention previously required `<!-- session-id: ... -->` as an HTML comment in the PR body. GitHub MCP's create_pull_request and update_pull_request both unconditionally strip HTML comments from the body field before persisting (confirmed across PRs #12 and #17), so the convention was mechanically unenforceable by the agent. Fix (ENG-25 option 2): the canonical marker shape is now a plain-text line: session-id: sesn_xxxxxxxxxxxxxxxx which survives MCP. The webhook regex still accepts the legacy HTML form for back-compat with PRs opened before this change. Changes: - app/api/github-webhook/route.ts:18-26 — extractSessionId tries HTML comment first, falls back to plain-text on its own line via ^\s*session-id:\s*((?:sthr_|sesn_)[A-Za-z0-9]+)\s*$ with the /m flag. Anchored to line boundaries so inline prose mentioning "session-id:" doesn't false-match. - .claude/memory/conventions/pr-session-id-marker.md — rewritten to specify the plain-text shape and explain the HTML-comment legacy. Also folds in ENG-27 (drops the duplicate trailing paragraph introduced by the PR #16 merge). - .claude/memory/learnings/2026-05-26-github-mcp-strips-html-comments.md — updated to note that create_pull_request strips too (confirmed on PR #17) and that ENG-25 is resolved by the convention change. - .claude/memory/MEMORY.md — index entries updated. Verification: - 12-case regex unit harness (plain shape, HTML shape, both shapes precedence, leading/trailing whitespace, inline-prose guard, bad prefixes, empty/null body) — all pass. - npm run lint — clean. - npm run build — green; /api/github-webhook still compiles as a dynamic route. - This PR body itself uses the new plain-text marker as the end-to-end test: if create_pull_request preserves the line and the webhook resumes on AGENT_REVIEW: APPROVED, the fix works in production. * memory: note greedy-strip behavior surfaced during PR #20 creation The MCP doesn't just strip well-formed comments — it strips greedily from any unclosed <!-- opener to end-of-body. Hit during PR #20's create_pull_request step (the PR meant to fix ENG-25 ate its own trailer because the body referenced the literal opener token in backticks). Workaround: refer to the token by phrase in prose, never as a literal substring. * ENG-25 round 2: last-match regex + unit-test guardrails Reviewer (PR #20 round 1) caught that String.match(/.../m) returns the first occurrence, so the code-block placeholder in this PR's own body would beat the real session-id trailer — breaking the documented 'last non-empty line' contract and the PR's stated end-to-end verification. Changes: - lib/extract-session-id.ts: extracted from route.ts as a pure module so it can be unit-tested without instantiating the Next route. Plain shape now uses matchAll(...) and takes the last match. HTML legacy shape kept first-wins (distinctive token, read-only-accepted). - app/api/github-webhook/route.ts: import the helper, drop the inline copy. - tests/unit/extract-session-id.spec.ts: 13 cases covering null/empty bodies, both shapes, both prefixes (sesn_ / sthr_), inline-prose guard, indentation tolerance, and the regression itself: 'placeholder in a fenced code block does NOT beat the real trailer'. - playwright.config.ts: testDir widened to ./tests, testMatch covers both e2e/ and unit/. Existing 12 e2e specs still discovered (verified via --list); 25 total tests now. - Memory: new learning at learnings/2026-05-26-regex-last-match-semantics.md on the /m vs /g + matchAll gotcha; MEMORY.md index updated. Verified: npm run lint, npm run build, npm run e2e -- tests/unit (13/13). --------- Co-authored-by: Self-Managing Codebase Manager <7004983+WillTaylor22@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes ENG-26 (https://linear.app/personal12o38452384576928345/issue/ENG-26).
Bug
app/api/github-webhook/route.ts:20extracts the PR session marker with a regex that requires thesthr_prefix. Butclient.beta.sessions.create()in the current@anthropic-ai/sdkreturns session ids with thesesn_prefix — every recent kickoff message and PR branch name carriessesn_, including this PR's session (sesn_01UTKGQsLg2sQsyGcZcBb9hN).So
extractSessionIdwas returningnullfor every well-formed marker the agent writes, andresumeOrFireManagerwas falling through to creating a fresh session on every webhook event. Session-resume has been silently non-functional since the SDK switched id prefixes.Fix
Webhook regex now accepts both prefixes via
(?:sthr_|sesn_). Keptsthr_for back-compat with any markers still around from before the SDK switch.Also updated:
.claude/memory/conventions/pr-session-id-marker.md— example now shows thesesn_prefix and explicitly notes both are accepted..claude/memory/MEMORY.md— one-line hook updated.See the diff for the exact regex change (3 files, +7/-3).
Verification
npm run lint— clean.npm run build— green;/api/github-webhookstill compiles as a dynamic route.nullfromextractSessionId) is the only path the bug took.Out of scope
ENG-25 (GitHub MCP strips HTML-comment substrings from PR bodies — including from inside code blocks) is the other reason the marker convention is currently brittle and is the reason this PR body cannot carry the session-id trailer. Orthogonal to the regex fix here.
Session id for this PR:
sesn_01UTKGQsLg2sQsyGcZcBb9hN(recorded in plain text since the standard HTML-comment trailer is stripped by MCP per ENG-25).