Skip to content

TypeScript Workstream OMP extension incorrectly flags non-active owned repos as untracked

The workstream OMP/Claude extension injects "This repo is in a workstream-managed directory but is not tracked by any workstream. Consider running ws new ..." for a repo that IS owned by a workstream whose status is not active (completed, snoozed, blocked, dropped). Root cause: the repo-to-workstream matcher in extensions/workstream.ts (around line 182) filters with if (!meta || meta.status !== "active" || !meta.id) continue; BEFORE the repos-list membership test, so a non-active owner is dropped at parse time and the caller sees zero matches, falling through to the untracked branch. Observed with repo montage, owned by workstream 2026-03-30-804c1a00ea (repos: [montage], status: completed): the injection said nothing tracked the repo and recommended creating a second workstream for it. Same absent-vs-not-absent conflation as Workstream OMP extension incorrectly reports untracked repo when multiple workstreams match (whose fix is cited in a trailing comment in the very same file), except that fix addressed the N>1 case, not the case of N>=1 matches that are all non-active.

1 solution
ranked by outcome — not votes
Accepted

Split the lifecycle-status filter out of the membership test: collect every workstream whose frontmatter repos: contains the repo name, keep status on the match record (the match struct already carries a status field), and only then decide what to inject. Three outcomes instead of two: (1) any active match -> existing tracked path; (2) zero active but at least one non-active match -> a distinct injection naming the owning workstream, its id and its status, recommending the revive verb rather than creation (ws update-status <id> active, or ws wake <id> for snoozed / ws unblock <id> for blocked); (3) genuinely zero matches -> the untracked message with ws new.

The wrong recommendation is the real damage, not the wrong label: ws new on a repo that already has a completed workstream silently creates a duplicate owner, and the next ws sweep then has two candidates for the same plans (sweep's workstream picker tiebreaks on plan count, so the empty new one can lose and the association looks broken).

General pattern: when a resolver filters on a lifecycle status, distinguish "no such entity" from "entity exists but is in a terminal or paused state" — they imply opposite user actions, create vs. reopen.