Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: c4637240bc5e4299ae63d5802ec34b8e
Branch: dev/5-bot-assign-source-log
Base: 80bc66c
At: 2026-11-05T01:40:40Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
workflow.assign({ botId }) hands a card that a workflow lane holds straight to a Bot, and the lane that loses it records nothing — the source graph stays silent.
This is the second door into the same defect #2480/#2481 closed for re-matching: the re-match path (rematch/ingest) now writes one 任务已改投 · {task} → {to} line on the source lane, but the explicit "assign this card to that Bot" call never reaches that code.
Reproduction (live main 7f341a1b91aca95971e3c5634898794be0e41e09)
- A workflow lane
W1holds taskT:state.claims[T] = { kind: 'workflow', id: W1 }, andW1's graph has its normal lines. - Call
assign({ botId: '<a live Bot id>', taskId: T, title: T }).
What happens: assign() short-circuits at plugins/workflow/src/service.js:1308 —
if (body.botId) return answer(routeBot(body))— and routeBot() (plugins/workflow/src/service.js:922) only does rememberClaim(taskId, { kind: 'bot', id: bot.id }) + write(). It never calls the logSource() closure that #2481 added at :1829 for the re-match path, and the workflow(workflowId) item is never even looked up.
Result: the call returns { ok: true, matched: true, decision: { kind: 'bot', id: … } }, the claim moves to the Bot, and state.lanes[W1] gains 0 lines (source_delta: 0 on a real state file). The lane's graph and the Bot's view disagree about where the card went.
Same shape on the pre-#2481 base (f8ec27c2): the call path is identical, so this is not a regression from #2481 — it is the other door #2481 deliberately did not take (that PR's diff ends at the re-match handler).
Expected
One system line on the source lane, identical in shape to the #2480 fix:
kind: 'system'
key: 'Task re-matched · {task} → {to}'
text: '任务已改投 · {task} → {to}'
with {to} the Bot's roster name when the roster lists it and the id as fallback (the convention #2481 landed and #2475 uses for handoff.from). The destination's own state must not change; one write(), not two. When there is no lane holding the card (an unclaimed card assigned straight to a Bot), there is no source graph to write on and the call stays silent exactly as it is today.
Scope
plugins/workflow/src/service.js—routeBot()writes the source line when a non-Bot claim is being replaced; no change to the re-match path, torememberClaim, or to the write count.plugins/workflow/test/— a case that pins "assign to a Bot from a held lane leaves exactly one source line, and the line names the Bot", plus the two no-op cases (no prior claim; already held by that Bot).changelog.d/fragment. No docs change expected: invariant 8 already states the contract this call is failing to honour (Docs-not-needed:with that reason is fine if the code says so).- Not in scope:
core/client.js,patches/, the source-graph UI.
Dedupe (2026-11-05)
- Issue search
repo:ThinkFlowLab/FlyBot routeBot in:title,body→ 0 hits. reroute→ 6 hits, all closed:#1668,#2470,#2472,#2480,#2481(+#2210, unrelated handoff contracts). None covers thebotIddoor.- Live main grep:
routeBotatservice.js:922,logSourceonly at:1829-:1865(re-match handler). No issue and no code covers thebotIdpath.
Activity
HugoXK commentedon Nov 5, 2026
Release
Written-by: FlyBot Lead
Released: c4637240bc5e4299ae63d5802ec34b8e
Reason: merged as f295853 (#2487)
At: 2026-11-05T02:10:04Z