Make Fisherman's replicate mode trustworthy - #160
Open
DavertMik wants to merge 20 commits into
Open
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
…captures Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
…en recency Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
…egradation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
…ot clobber HTTP status Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
…result on exhaustion Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
…substitute types Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
…ated finish Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
…gment A record id in the page URL could be the rarest segment shared with the captured writes, making it the scope key and narrowing the endpoint list to that single record's endpoints. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQkCCTCWm6ajL7CKa4ekvy
…s name their request Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019JP2duAZr2ppH436viAJYs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019JP2duAZr2ppH436viAJYs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019JP2duAZr2ppH436viAJYs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019JP2duAZr2ppH436viAJYs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019JP2duAZr2ppH436viAJYs
# Conflicts: # CHANGELOG.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019JP2duAZr2ppH436viAJYs
Split the CSRF read's catch so a failed evaluate keeps the cookies Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019JP2duAZr2ppH436viAJYs
Drop the forced tool choice so a prose ending is a normal response instead of a 400 that discards completed write roundtrips and re-creates the data on retry. The model's text becomes the summary when writes succeeded; created items still come from the ledger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019JP2duAZr2ppH436viAJYs
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.
Fixes the five root causes behind every failed Fisherman episode in the April/June traces: the agent poisoning its own request store, first-match spec lookups, a scope filter that never matched, an unconditionally successful
finish, and silent exhaustion.Spec:
docs/superpowers/specs/2026-08-29-fisherman-reliability-design.md· Plan:docs/superpowers/plans/2026-08-29-fisherman-reliability.mdWhat changed
loadFromDisk()admits onlyxhr_*files — provenance was already encoded in the id prefix (browser capturesxhr_, Fisherman's own calls unprefixed), so no new envelope key and poisoned directories migrate for free. Behavioral change: aoutput/requests/dir containing only Fisherman-made files now reports replicate mode unavailable — correct, there was never real browser traffic to replicate.findCapturedRequestprefers exact endpoint over deeper sub-path, then 2xx over rejection, then newest. Paths are normalized with the existingisDynamicSegment(), so{id}patterns and concrete ids both match.finishgated by the request ledger. Success requires at least one real 2xx write in this run; claimed created ids are verified against the requests that actually returned them (RequestMapinsrc/utils/request-map.ts), unverifiable claims are dropped, and each verified item names its creating request (request: "POST /api/…", logged by Pilot asvia POST /api/…). A run that ends withoutfinishsynthesizes an honest summary from the ledger instead of returningsummary: ''.tests/integration/fisherman.test.ts, aimock) covers the scoped prompt and the ledger gate end-to-end; unit coverage for ranking, scoping, load filtering, and ledger results.Live-session auth (2026-08-30)
Langfuse trace
de95bd1cffce09169599d99d1bee56cd(beta.testomat.io) showed every Fisherman write rejected403 {"error":"Unauthorized"}in ~8ms while the same browser session performed successful writes to the same project minutes earlier. The requests were doomed by Fisherman's own credential assembly: an unfiltered cookie-jar dump with a dead localhost session pair ahead of the valid one, plus anx-csrf-tokenscraped from a 7-week-old capture of another project (the store's "newest" iteration was really alphabetical filename order).Principle now enforced (spec:
docs/superpowers/specs/2026-08-30-fisherman-live-session-auth-design.md): captures are a source of API shape, never credentials. Credentials come from the live browser session or explicitapi.headersconfig.extractAuthHeadersis session-gated: only captures made during this session, newest first by timestamp;cookieis never scraped from captures; capture files without timestamps read as stale.refreshAuthprecedence is captured < live browser < config, and the browser-derived layers apply only in replicate mode — achieve mode authenticates solely throughapi.headers. Behavioral change:Authorization/x-api-keyvalues from previous sessions' captures are no longer sent; long-lived tokens belong inapi.headers.context.cookies(baseEndpoint); the live CSRF token is read from the page'smeta[name="csrf-token"](provider renamed tobrowserHeaderProvider). A failed CSRF read degrades to no token without discarding the cookies.isDynamicSegmentgained a version-segment carve-out:v1/v2-style segments are never treated as ids, soPOST /api/v1/suitesstays literal in endpoint lists.finish(tool choice no longer forced): gpt-oss-style models that close with prose or ajsonpseudo-tool no longer 400 the whole invocation — which previously discarded the completed write roundtrips and re-created the same data on every retry (4 duplicate suite+test pairs in trace7d5ddba053a054f6a3725503c9569340).Review follow-ups deliberately left out of this PR: dedupe of the run-ledger watermark (
fisherman.ts/fisherman-tools.ts),RequestMap's last-write-wins attribution when two responses return the same id (report text only), redacting auth headers in the persistedoutput/requests/*.yamlfiles (pre-existing behavior), andoauth2/hex-word segments still classifying as dynamic.Regression fixture: deliberately NOT enabled
The plan's rollout step called for flipping
fisherman: { enabled: true }intests/regression/fixture/explorbot.config.js, gated on verifying the fixture can feed replicate mode. It cannot: the Trackly fixture has zero fetch/XHR — all UI writes are native form posts (form.requestSubmit()inclient.js), whichXhrCaptureignores by resource type and content type. Replicate mode would see zero captures, so the flip would prove nothing. Options:apiblock to the regression config — the fixture already serves/api/openapi.json, so achieve mode works today, but that exercises a different code path than these fixes target.fetchwith JSON — exercises replicate mode, but alters fixture behavior for every regression scenario.Until one of those lands, the plan's acceptance criterion (a trace where
precondition()ids appear on the page the Tester acts on) is blocked on that decision. Regression runs remain user-triggered only — apply theregressionlabel when you want one.🤖 Generated with Claude Code
https://claude.ai/code/session_01MHhNTXzihYU8f8mqNdjrVP
https://claude.ai/code/session_019JP2duAZr2ppH436viAJYs