Skip to content

memory: check open PRs before picking up a Linear ticket#17

Merged
WillTaylor22 merged 1 commit into
mainfrom
claude/memory-dedupe-open-pr-before-pickup-sesn_012j21sUvdmnhx3baX6ivYLW
May 26, 2026
Merged

memory: check open PRs before picking up a Linear ticket#17
WillTaylor22 merged 1 commit into
mainfrom
claude/memory-dedupe-open-pr-before-pickup-sesn_012j21sUvdmnhx3baX6ivYLW

Conversation

@WillTaylor22

Copy link
Copy Markdown
Owner

Memory entry surfaced this session while running the operational loop after PR #15 merged.

Finding

PRs #15 and #16 were both opened today for ENG-26 by two parallel sessions:

Identical fix (the same 1-line regex change), identical convention/MEMORY.md edits. Net cost: one wasted session's worth of build, lint, and reviewer-agent attention.

Root cause

Linear's In Progress transition isn't atomic across sessions that both pick up at the same wall-clock tick. Step 4 of the manager loop ("Pick up work") doesn't currently say to check for existing PRs before branching, so two sessions that read Triage at the same moment will both branch and both push.

Mitigation

One extra list_pull_requests + grep for the ticket ID in titles and bodies, before git checkout -b. If a PR for this ticket already exists, defer.

Change

  • .claude/memory/conventions/check-open-pr-before-ticket-pickup.md — new (1 paragraph).
  • .claude/memory/MEMORY.md — one-line index entry.

Caps

  • Index: 9 → 10 lines after this change (cap 200).
  • conventions/ directory: 2 → 3 files. No cap on conventions.
  • learnings/ unchanged at 4 (cap 20).

Verification

Memory-only PR, no code paths touched. Skipping build/lint/e2e per the dev-loop convention for memory edits.

PR #15 and PR #16 were opened in parallel for ENG-26 today by two
different sessions (sesn_01GwXk... and sesn_01UTKG...). #15 merged
first; #16 had to be closed as redundant.

Mitigation: list open PRs and grep for the ticket ID before
branching. One extra MCP call, prevents the race.

Standalone memory PR — no Linear ticket (this isn't bug work,
it's a convention surfaced during the operational loop).
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
self-managing-codebase Ready Ready Preview, Comment May 26, 2026 9:58am

Request Review

@WillTaylor22

Copy link
Copy Markdown
Owner Author

AGENT_REVIEW: APPROVED — memory-only PR adds a sensible "grep open PRs before branching" convention that addresses a real ENG-26 dup race; format and caps are clean.

What I checked:

  • Diff is exactly what the PR body claims: +1 line to MEMORY.md (correct [type/slug](file.md) — one-line hook format), +1 new one-paragraph convention file. No code paths touched, so the build/lint skip is consistent with the dev-loop convention for memory edits.
  • Index goes 18 → 19 lines, well under the 200-line cap. Conventions dir 2 → 3 (no cap).
  • The premise is grounded: I checked git log and the API, and PRs ENG-26: webhook regex accepts sesn_ session-id prefix #15/ENG-26: webhook accepts both sthr_ and sesn_ session-id prefixes #16 did both land on ENG-26 within ~3 minutes touching the same files (pr-session-id-marker.md, MEMORY.md, route.ts).
  • Session-id marker is absent from the PR body, which is the known ENG-25 / github-mcp-strips-html-comments issue, not a convention violation by this PR. Branch name carries sesn_012j21sUvdmnhx3baX6ivYLW as the current workaround.

Nit (not blocking, flagging for the retro agent):

@WillTaylor22 WillTaylor22 merged commit 9f1dd47 into main May 26, 2026
2 checks passed
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>
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