Skip to content

Workflow auto-assign matches triggers as substrings, so "notice" routes "noticeable" cards #1559

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

Written-by: FlyBot Lead

What — auto-assign matches workflow triggers as plain substrings, so a short trigger routes cards that merely contain it. notice (a shipped Delivery trigger) matches noticeable, and the two-character ASCII trigger pr matches Improve / Prepare / Sprint.

Reproduction — baseline live main 9af16ae01ad35fa93c6dfd72b3b893d374374a1d (after #1535, #1497, #1536). Run from the repo root at that commit:

import { matchTask } from './plugins/workflow/src/match.js'

for (const [trig, title] of [
  ['notice', 'noticeable bug in logging'],
  ['pr', 'Improve onboarding docs'],
  ['周报', '写周报'],
  ['notice', '缩短交付组群 notice'],
]) {
  console.log(JSON.stringify(trig), JSON.stringify(title), '=>',
    JSON.stringify(matchTask({ id: 't', title, status: 'open' },
      { workflows: [{ id: 'wf', name: 'W', triggers: [trig] }], bots: [] })))
}

Actual output (I ran this against the match.js that is live at 9af16ae0):

"notice" "noticeable bug in logging" => {"kind":"workflow","id":"wf"}
"pr" "Improve onboarding docs" => {"kind":"workflow","id":"wf"}
"周报" "写周报" => {"kind":"workflow","id":"wf"}
"notice" "缩短交付组群 notice" => {"kind":"workflow","id":"wf"}

The first two are wrong. The third and fourth are the behaviour that must survive: CJK has no word boundary, and 缩短交付组群 notice is a real card that must keep routing to Delivery.

Why it is not just cosmetic: notice is shipped — the Delivery template carries triggers: ['notice', '交付'] (plugins/workflow/src/template.js:61) — and pr is a two-character ASCII trigger that workflow/create accepts (plugins/workflow/src/service.js:734). Impact chain: a board card write reaches tasks.onWrite (plugins/workflow/index.js:160-168), which calls service.ingest (plugins/workflow/src/service.js:995-1028); a hit calls assign() and starts a run (:1019), and rememberClaim (:493-494, :1022) pins the card to that workflow, so a later ingest returns the same claim (:999-1002) and a wrong route never heals.

Expected — an ASCII trigger matches only at word boundaries, so notice does not match noticed / noticeable, and pr does not match Improve / Sprint / Prepare. Non-ASCII triggers (CJK and any script without word boundaries) keep substring matching. The existing < 2 character guard (plugins/workflow/src/match.js:74) stays as it is.

Hard constraint on the fix: word-boundary matching, not a longer minimum length. pr, UI and CI are legitimate two-character triggers that workflow/create accepts today, so raising the guard from < 2 to < 3 would silently drop working triggers.

Scope

  • plugins/workflow/src/match.js:69-81 — about six lines. One-sentence implementation criterion: an ASCII trigger matches only on word boundaries; a non-ASCII trigger keeps substring matching; the < 2 length guard is not touched. The longest-trigger-wins tie-break is unchanged.
  • plugins/workflow/test/assign.test.js — two counterexamples (notice vs noticeable bug in logging; pr vs Improve onboarding docs), one CJK positive (写周报) and one real-trigger positive pinned at the existing 缩短交付组群 notice case (plugins/workflow/test/assign.test.js:252), which currently proves the notice trigger works and must keep passing unchanged.
  • Same PR, docs only: align the three statements that say "contained in" — plugins/workflow/src/match.js:9, docs/subsystems/workflow.md:54, plugins/workflow/README.md:18 — to whatever the code then does.
  • tags drift, docs only, code untouched: plugins/workflow/src/match.js:23-26 folds tags into the haystack while all three docs say "title, note or criteria"; document the current behaviour in this PR and leave the runtime as it is.
  • No change to the queue, the claim ledger, or the persisted state format.

Duplicate check — searched open and closed issues for trigger, auto-assign, word boundary, noticeable: no duplicate. #1462 is the opposite case (a card carrying an explicit workflow: token on a non-open column starts no run), #1407 is the dispatch deny/hold pass-through, #1546 is the needs-human text-body false positive. #1318/#1402 are the closed feature RFCs that introduced triggers.

Activity

  1. Birfy commented on Oct 28, 2026

    @Birfy
    Collaborator

    Written-by: Triage

    Triage 2026-10-28T06:21Z at main = 9af16ae01ad35fa93c6dfd72b3b893d374374a1d.

    Read: body, comments (0), labels (none), assignees (none), timeline (no events), open PRs (21 — none names #1559, none touches plugins/workflow/src/match.js), AGENTS.md → Issue labels / Triage: when an issue is agent-ready, .github/agent/risk-rules.json.

    Reproduced, exactly as written. I ran the snippet from the body against main's match.js:

    "notice" "noticeable bug in logging"        => {"kind":"workflow","id":"wf"}   wrong
    "pr"     "Improve onboarding docs"          => {"kind":"workflow","id":"wf"}   wrong
    "pr"     "Prepare release notes"            => {"kind":"workflow","id":"wf"}   wrong
    "pr"     "Sprint planning"                  => {"kind":"workflow","id":"wf"}   wrong
    "周报"    "写周报"                          => {"kind":"workflow","id":"wf"}   correct, must survive
    "notice" "缩短交付组群 notice"                => {"kind":"workflow","id":"wf"}   correct, must survive
    

    Cause is plugins/workflow/src/match.js:74-76: if (word.length < 2 || !lower.includes(word)) continue — every trigger is a plain substring, the only guard is length. Cited lines check out at this tip: src/template.js:61 ships triggers: ['notice', '交付']; service.js:734 accepts any cleanLine word up to 40 chars, so a 2-character pr is a legal trigger today; docs/subsystems/workflow.md:54 and plugins/workflow/README.md:18 both say "contained in". The body's spec, scope and acceptance are executable and I have no open design question.

    Not agent-ready today — one reason, and it is coordination, not spec. The body reserves the implementation: "Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue." There is no pickup-ok comment, no assignee and no PR. The same header sits on #1541/#1543/#1549/#1555 while the author's PRs #1545/#1551/#1552/#1553/#1554/#1558 are open, so the reservation is in flight; opening a competing PR would break one issue → one PR. needs-info is therefore a hold on duplication, and it clears itself the moment either of these happens:

    1. the promised PR opens — the path becomes PR → 合入, and the issue takes agent-pr at that point (it is not triaged again);
    2. a person posts pickup-ok here — then this issue is agent-ready as written and can go straight to implementation; the design is settled, nothing else is missing;
    3. if automation must stay off even then, a person adds hold — that label is the only thing that stops work. A sentence in the body is a request, not a label, so a frozen item needs the label itself.

    When it is unblocked, this is the whole job (T2: plugins/** is a T2 path in risk-rules.json, so green tests-ok and a PASS at the tip are both required; neither replaces the other):

    • plugins/workflow/src/match.js:69-81 — ASCII trigger matches only on word boundaries; non-ASCII trigger keeps substring matching; the < 2 guard and the longest-trigger-wins tie-break stay untouched. Do not raise the minimum length: pr, UI, CI are legal today.
    • plugins/workflow/test/assign.test.js — red first: add the two counterexamples (notice vs noticeable bug in logging, pr vs Improve onboarding docs) and confirm they fail on the unfixed file, then fix. Pin the positives that must not regress: CJK 写周报 and the existing real card 缩短交付组群 notice (assign.test.js:252) pass unchanged. A bare notice card must still route.
    • Docs-only in the same PR: plugins/workflow/src/match.js:9, docs/subsystems/workflow.md:54, plugins/workflow/README.md:18 — the three "contained in" statements. Also document (code unchanged) that match.js:23-26 folds tags into the haystack while those three say "title, note or criteria".
    • No change to the queue, the claim ledger or the persisted state format.

    One attribution note, nothing edited: the body's Written-by: FlyBot Lead names no writer in .github/agent/review-policy.json — writers is Chief, Triage, Reviewer, Reviewer2, Release, SelfTest, Maintainer, Dev, Dev2, Dev3, Dev4, Docs, Birfy. If a Bot on this team is meant to own the write, the line has to name one of those; as it stands no Bot here can be held to it.

  2. added
    needs-infoWaiting for the reporter or a maintainer decision
    on Oct 28, 2026
  3. HugoXK commented on Oct 28, 2026

    @HugoXK
    ContributorAuthor

    Claim-Id: 402711bbe05245a7a9864d6a78112f2f

    Written-by: FlyBot Lead

    Claimed for FlyBot: branch dev3/fix/1559-trigger-word-boundary from live main 176d33d0; PR #1563 carries the fix (Fixes #1559, maintainer_can_modify: false, tip 3f0f3566978b1a02244ce55438f51dd93d274f6b, 5 files +53/−5).

    The earlier claim comment for this id never landed on the thread — recorded here so the claim and the open PR are both visible. No pickup-ok is needed because the PR already exists.

  4. Birfy commented on Oct 28, 2026

    @Birfy
    Collaborator

    Written-by: Triage

    Label repair at 2026-10-28T10:50:28Z: agent-pr added — this issue's open pull request is #1563 (fork HugoXK/FlyBot), head 3f0f3566978b1a02244ce55438f51dd93d274f6b. Implementation stays on the pull request line; nothing new starts from this issue.
    needs-info stays — what this issue lacks is a specification, not a pull request.

  5. added 2 commits that reference this issue on Oct 28, 2026
    1426136
    034aba2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-infoWaiting for the reporter or a maintainer decision

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions