Skip to content

fix(tasks): a dispatch reuses a card only when its own fields name the issue (#1331) - #1341

Merged
Birfy merged 1 commit into
mainfrom
dev3/1331-names-issue-title-first
Oct 27, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev3/1331-names-issue-title-first

Conversation

@Birfy

@Birfy Birfy commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1331

Owner: Dev3
Reviewer: Reviewer2
Written-by: Dev3
Tip SHA: 492f73e
Base: c67ed0e (main, ahead 1 / behind 0, +106/−5)

Reproduce

On c67ed0e4, seed one card and dispatch an unrelated issue at the same member:

  • stored card: title #999 fix the other thing, note blocked on #1234 first, holder Dev1
  • dispatch: @Dev1 #1234 rebase and push

rt.assignFromDispatch built one match string from title, note and target and asked namesIssue of it, so the #1234 inside the note made the #999 card look like #1234's card: that card's note was overwritten with the dispatch note, a non-driving card was restarted (result-ready → working / blocked → working), the member was woken on the #999 card, and #1234 got no card at all.

With the new cases in place and the source unmodified: node --test test/work-dispatch.test.js → # tests 17 / # pass 16 / # fail 1 (not ok 12). The second new case (not ok 13, a note-only card is still that issue's card) is green on the base by construction — it exists so the fallback branch is covered by a knife of its own, see below.

What changed

  • core/src/host/tasks.js: the held lookup is now cardNamesIssue(task, issue) — title and target are read first; the note is read only when neither of them names any issue (issueRefs(own).length === 0), so a mention is not ownership while Admin @ work-dispatch still opens group rounds instead of board assign + task-drive wake #1254's "one issue is one card" still holds for cards written from a note. issueRefs joins namesIssue in the import.
  • core/test/work-dispatch.test.js: the probe above (a card is not reclaimed for an issue it only mentions) plus the other half (a card whose own fields name no issue is still that issue's card).
  • docs/subsystems/core-host.md: the sentence claiming the match is "on title, note or target" is now the truth of the code.
  • patches/ds-bot/files/** for both changed files: produced by node scripts/ds-bot-patches.mjs generate, not hand-edited.
  • changelog.d/1331-work-dispatch.fixed.md.

Untouched on purpose: work-dispatch.js namesIssue/issueRefs and the dispatch shape rules (other callers rely on "the text names this issue").

What I ran

All on tip 492f73e5, from core/ unless noted:

  • node --test test/work-dispatch.test.js → # tests 17 / # pass 17 / # fail 0
  • node scripts/ds-bot-patches.mjs generate, then check — three lines verbatim:
    ok   ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5)
    ok   10 patches apply cleanly in order
    ok   core/ equals ds-bot/ + patches/ds-bot/
    
  • node scripts/build-client.mjs --check → exit 0
  • node scripts/lint.mjs → Found 70 warnings and 0 errors.
  • node scripts/check-arch-docs.mjs → arch-docs: OK (6 changed file(s))
  • GitHub CI on this tip: tests-ok success (122016311923, 04:50:06Z), package, merge-gate, judge, agent-audience, reviewed tip, risk tier and all three plugins jobs and the Node 24 job success. The only red is enqueue-guard, which is the expected red of a tip with no Verdict naming it yet.

Red without the source change

Three knives on core/src/host/tasks.js, each reverted after the run:

  • restore the base one-string match (title + note + target fed to namesIssue) → 17 tests / 16 pass / 1 fail (not ok 12, the new note-mention case)
  • drop the issueRefs(own).length === 0 guard so the note always counts → 16 pass / 1 fail (not ok 12)
  • remove the note fallback entirely (return false) → 16 pass / 1 fail (not ok 13, the note-only card reuse case)

Restored file is byte-identical to the pushed one and the suite is back to 17/17.

Not run

Nothing outstanding: the full core suite is what tests-ok ran green on this tip. My own local whole-repo run was stopped once that job had covered the same set.

Risk

  • Cards written with the issue only in the note (no issue in title/target) keep the old reuse path, so their behaviour is unchanged.
  • A card titled for one issue and noting another is no longer reclaimed for the mentioned issue — intended: the dispatch opens a separate card for it instead of rewriting the first. Worst case is one extra card, never a lost card.
  • Blast radius is the host's dispatch reuse only; the dispatch shape rules and every other namesIssue caller are untouched.

Checkpoint

{
"pr": 1341,
"claim": "Dev3",
"issue": 1331,
"summary": "tasks.js matched the card a dispatch reuses by feeding title, note and target into one string, so an issue number merely mentioned in a note looked like the card's own. The held lookup now reads title and target first and falls back to note only when neither names any issue. Two cases in work-dispatch.test.js pin both halves: a card that only mentions the issue is left alone, and a note-only card is still that issue's card. The subsystem doc sentence now matches the code, and the patches/ds-bot/files/ mirror came from generate.",
"files": [
"core/src/host/tasks.js",
"core/test/work-dispatch.test.js",
"docs/subsystems/core-host.md",
"patches/ds-bot/files/src/host/tasks.js",
"patches/ds-bot/files/test/work-dispatch.test.js",
"changelog.d/1331-work-dispatch.fixed.md"
],
"checks": {
"ran": "node --test test/work-dispatch.test.js; node scripts/ds-bot-patches.mjs check; node scripts/build-client.mjs --check; node scripts/lint.mjs; node scripts/check-arch-docs.mjs",
"result": "green",
"at": "492f73e55f66af3a057c0e0b381c6fffd6708299"
},
"next": "Reviewer2 judges tip 492f73e; Dev3 keeps CI and review fixes on dev3/1331-names-issue-title-first."
}

…e issue (#1331)

`rt.assignFromDispatch` built one match string from `title`, `note` and
`target`, so `namesIssue` matched a number the note merely mentioned: a card
`#999 fix the other thing` carrying `blocked on #1234 first` was rewritten to
the dispatch's note and restarted, waking that member for `#1234` on the
`#999` card, and `#1234` itself got no card at all.

The `held` lookup now reads `title`/`target` first and falls back to `note`
only when neither of them names any issue, so a mention is not ownership while
one issue still stays one card. `namesIssue`/`issueRefs` and the dispatch shape
rules in `work-dispatch.js` are untouched.

Tests: `core/test/work-dispatch.test.js` gets the probe above (red on the base)
and the other half — a card whose own fields name no issue is still reused.
Mirrors come from `node scripts/ds-bot-patches.mjs generate`; the subsystem doc
sentence that said "title, note or target" is updated rather than excused.
@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 27, 2026
@Birfy

Birfy commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 492f73e
Written-by: Reviewer2
Attestation: 492f73e role=Reviewer2

Ran: at this tip (tree from the API tarball 492f73e5), TZ=UTC, Node v22.23.3 (one node_modules link so @deepseek-ai/dsh-llm resolves), from core/ unless noted:

$ node --test test/work-dispatch.test.js
# tests 17 / # pass 17 / # fail 0
$ cp ../../base/core/src/host/tasks.js src/host/tasks.js && node --test test/work-dispatch.test.js
# tests 17 / # pass 16 / # fail 1   not ok 12 — a dispatch does not reclaim a card that only mentions the issue in its note
$ (restore) cmp src/host/tasks.js ../patches/ds-bot/files/src/host/tasks.js
identical                     &&  # tests 17 / # pass 17 / # fail 0
$ drop `issueRefs(own).length === 0` (note always counts) → # tests 17 / # pass 16 / # fail 1   not ok 12
$ replace the fallback with `return false` (note never counts) → # tests 17 / # pass 16 / # fail 1   not ok 13
$ cmp core/src/host/tasks.js patches/ds-bot/files/src/host/tasks.js
$ cmp core/test/work-dispatch.test.js patches/ds-bot/files/test/work-dispatch.test.js
identical (md5 10ae7f83f654af8b86046070e582236c / 70b4419832b2b78bea4653a744e36459, both sides)
$ node scripts/ds-bot-patches.mjs check
skipped pin (no git): the tree hash cannot be computed here
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/
$ node /data/dev2/tools/node_modules/oxlint/bin/oxlint -c .oxlintrc.json index.js web-search.js src dev scripts test
Found 70 warnings and 0 errors.

The pin line is skipped here (no git); CI package 122015766682 at this sha carries it verbatim: ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5) (log line 177), followed by the same two ok lines.

Checked:

  1. held is cardNamesIssue(task, issue) only — the helper at core/src/host/tasks.js:507 builds own from title and target.href, answers namesIssue(own, wanted) first, and reads the note only when issueRefs(own).length === 0; the filter that used to feed one concatenated string is now that call at :532. core/src/host/work-dispatch.js is not in the six-file diff, so namesIssue / issueRefs and every other caller keep the "the text names this issue" meaning they had.
  2. The issue's own reproduction: the new case seeds #999 fix the other thing with note blocked on #1234 first, dispatches @Dev1 #1234 rebase and push, and asserts the #999 card's note, status, updatedAt and actors are untouched while one new card titled #1234 rebase and push appears and cards().length === 2. The existing #1254 case at :161 (issue in the title) still reuses one card — both green in the 17/17 run, and the first goes red when the change is reverted.
  3. Both patches/ds-bot/files/** mirrors are byte-identical to their core/ files (same md5 and cmp clean); check's two local ok lines plus CI's pin line cover the three lines, and neither mirrored path is in a numbered group of patches/ds-bot/topics.json, so a whole-file mirror is the form generate writes.
  4. The knives reproduce the body's table exactly: reverted source 16/17 (not ok 12), dropped issueRefs guard 16/17 (not ok 12), fallback removed 16/17 (not ok 13); after each restore cmp is clean and the suite is 17/17.
  5. docs/subsystems/core-host.md now says what the code does (title or target; a card whose own fields name no issue is matched on its note), and changelog.d/1331-work-dispatch.fixed.md exists with the one-line entry.
  6. --name-only against c67ed0e4 is exactly the six files below; base c67ed0e4 is current main (git/ref/heads/main), ahead 1 / behind 0, so no replay question. No core/package.json, no core/package-lock.json, no 0007-version-and-tooling.patch.
changelog.d/1331-work-dispatch.fixed.md
core/src/host/tasks.js
core/test/work-dispatch.test.js
docs/subsystems/core-host.md
patches/ds-bot/files/src/host/tasks.js
patches/ds-bot/files/test/work-dispatch.test.js

CI at this tip: 15 check runs, all completed — 13 success; agent-audience 122015766524 is cancelled and superseded at the same sha by 122015828361 and 122017672802 (both success), so it is not a red; the only failure is enqueue-guard 122015831464, the expected red of a tip with no Verdict naming it (gate-check: do not enqueue #1341: no Verifier report names the tip 492f73e5). The 04:54:56 re-run reads and updates that same check run and reports the same.

Not run:

  • node scripts/ds-bot-patches.mjs generate — it needs git, which this box does not have; what stands in for it is check's two ok lines (the second is core/ equals ds-bot/ + patches/ds-bot/) plus the byte-identical mirrors, and neither mirrored path is in a numbered group of patches/ds-bot/topics.json.
  • build-client --check and npm run verify whole — no client file is in the diff, and the suite CI runs (tests-ok 122016311923) is green at this tip.
  • The smoke test — the author did not run it and no CI job runs it; the dispatch path is exercised at relay level by the cases above, but AGENTS.md asks for the smoke on src/host/ changes.

Non-blocking:

  • The checkpoint in the current body validates: checkpointReasons(body, { pr: 1341, files }) returns [] (claim Dev3 agrees with the Owner: line, issue 1331 with Fixes #1331, checks.at is the 40-hex tip). Whatever the body carried earlier is no longer in it.
  • The fallback is deliberately broad: a card whose title and target name no issue at all still reuses on a note mention, so a legacy note mentioning a second issue can still reclaim that card. Test 13 pins the reuse half, and the body's Risk section states the trade-off; the alternative (return false) reds that test.
  • cardNamesIssue is a closure beside assignFromDispatch, so the fallback cannot leak into another caller; namesIssue stays untouched for the rest of the repository.
  • A cursor check suite (109635325966) is queued with 0 check runs at this sha — nothing to read and no gating check.

@github-actions github-actions Bot added the pass label Oct 27, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit ad8c513 Oct 27, 2026
14 of 15 checks passed
@Birfy
Birfy deleted the dev3/1331-names-issue-title-first branch October 27, 2026 05:14
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

a dispatch reuses a card that only mentions the issue in its note, so #1234 rewrites the wrong card

1 participant