← All needs

wayfinder: claim protocol fails under concurrent sessions (double-resolution + map lost-update), and one-ticket-per-session is easy to violate

Observed in a real multi-session wayfinder run on a GitHub-tracker repo (map + 9 child tickets, several Claude Code sessions working the frontier in parallel). Two distinct failures, both traceable to gaps in `wayfinder/SKILL.md` (and the GitHub tracker doc's "Wayfinding operations"). ## Failure 1: two sessions resolved the same ticket Timeline (one research ticket, "3"): 1. Session A (Claude Code on the web; writes to the tracker via the GitHub MCP **as the repo owner's user**) queried the frontier: 3 was open, **unassigned** → claimed it by self-assigning, per the skill. 2. Session B (running as the **`claude[bot]` GitHub App**) was *already working 3* — but no assignee was ever visible on the issue. Either it skipped the claim step or an App identity can't put a user in the assignee field (and nothing surfaced the failure). 3. Both sessions posted resolution comments **one second apart** (18:25:44 / 18:25:45), the issue was closed twice-over, and two near-duplicate research assets landed on two branches. Why the skill's claim protocol didn't prevent it: - **Claim-by-assignee is the only signal, and some sessions can't emit it.** The skill says "that assignee _is_ the claim: an open, unassigned ticket is unclaimed" — but a bot-identity session that can't (or doesn't) assign is invisible, and every reader correctly concludes the ticket is free. - **All sessions driven by one human assign the *same* user**, so even when claims do land, Session A cannot distinguish "my claim" from "another session's claim" on a later read. - **No re-verification before resolving.** The skill has a read→claim TOCTOU window and, worse, a long claim→resolve window. Nothing instructs a session to re-fetch the ticket immediately before posting the resolution and abort/reconcile if it's already closed or carries a rival resolution or claim. ### Collateral: map lost-update Decisions-so-far is maintained by rewriting the whole map body. During the same run, a concurrent session updated the map from a stale snapshot (it was only fixing a link in its own earlier entry) and **clobbered another session's freshly appended decision line** — the closed ticket silently vanished from the index until a human noticed. ### Suggested fixes 1. **Two-part claim**: assignee (where possible) **plus a claim comment** on the ticket (`Claimed — session , `). The comment works for identities that can't assign and disambiguates sessions sharing one user account. Frontier query treats *either* signal as claimed. 2. **Verify-before-resolve**: prescribe a re-fetch of the ticket immediately before posting the resolution comment; if the ticket is closed, or has a rival claim/resolution comment newer than your claim, stop and reconcile instead of posting. 3. **Map edit discipline**: re-fetch the map body immediately before every edit, change only your own line(s), and re-read after writing to detect a clobber. (Or move Decisions-so-far to per-decision comments on the map so appends can't collide.) ## Failure 2: session resolved one ticket, then claimed the next After Session A resolved 3 (including reconciling the race above), the user sent a short follow-up ("that session died — reclaim this ticket and finish this session"). The agent pattern-matched this to "Work through the map" step 2 ("Otherwise take the first frontier ticket in order"), claimed the **next** frontier ticket (4), and started a research agent for it — a straight violation of "**never resolve more than one ticket per session**". The user had to intervene; the claim was rolled back. Why the rule got violated: - The one-ticket rule appears once, at the top of **Invocation**, but nothing in "Work through the map" marks a terminal state. After step 5 the session has no "you are done claiming" guard, so any later user message in the same session gets interpreted through the step-2 lens ("pick the next frontier ticket"). - Ambiguous mid-session instructions ("finish this session") resolve, by default, toward *more* work rather than toward wrap-up. ### Suggested fix Add an explicit terminal state to "Work through the map", e.g. after step 5: > **6. The session is now spent.** Bookkeeping on the already-resolved ticket (reconciliation, link fixes, map index repairs) is still allowed, but this session must not claim another ticket — even if asked to continue, respond that working a new ticket needs a fresh `/wayfinder` invocation. That makes the one-ticket rule self-enforcing at the moment it matters, instead of relying on the agent remembering a line from the preamble. ## Environment - Tracker: GitHub issues, native sub-issues, claim-by-assignee per the GitHub tracker doc's "Wayfinding operations". - Sessions: Claude Code (web/remote), one writing as the repo-owner user via the GitHub MCP, one as the `claude[bot]` App. - Outcome was recoverable: the duplicate resolutions agreed (accidental cross-verification) and were consolidated into one canonical asset — but only because a human spotted both failures.