Originally created by: Akarsh-Hegde
GitHub Project sync broke for users whose board columns didn't match our three hardcoded status buckets. Every provider collapsed its native status into todo / in_progress / done — GitHub via substring guessing, Jira/Linear/Azure via a fixed map — and discarded the real column name. Any custom GitHub column the matcher didn't anticipate ("In Review", "QA", "Ready for Deploy", "Shipped") silently became todo, so the product's lanes no longer matched the user's board.
The reported bug is GitHub-specific in practice because GitHub Projects v2 single-select fields expose only the raw column name — there is no semantic category — whereas Jira/Linear/Azure ship a separate, fixed done/closed signal alongside the custom name. (Verified against Atlassian docs: Jira boards do have arbitrary custom columns and statuses; what they additionally provide is the non-customizable statusCategory, which is why our Jira path never had to guess. That category is reliable for new/indeterminate/done but can be undefined on JSM / self-hosted Server/DC — so it isn't infallible either.)
Replace the bucketed status_category with two columns on pm_tasks:
| column | role |
|---|---|
status_raw |
verbatim provider column/state name, shown to the user as-is |
is_terminal |
the one normalized signal logic needs: is this ticket done/closed? |
A new shared resolver (src/intelligence/providers/status.rs) is used by every provider, with precedence:
<PROVIDER>_TERMINAL_STATUSES / <PROVIDER>_OPEN_STATUSES (comma-separated, case-insensitive). The per-board correction path; an override always wins.statusCategory, Linear state type, Azure StateCategory. Preserved where reliable; Jira's undefined defers to the heuristic instead of blind-bucketing.ground.rs, hooks.py): the ticket_closed flag now keys off is_terminal, not status == "done". collect.rs + SessionBundle (Rust & Python) carry pm_task_is_terminal.run_task_linker_mlx.py): candidate-task query now selects status_raw + is_terminal (the raw name is better prompt context anyway).StatusPill): renders the raw name verbatim, colored by terminality; never collapses unknown columns to "Todo".status_raw. (It had been selecting a status column that migration 022 dropped — a latent bug fixed here.)status_category.pm_status_map table / dashboard editor yet — v1 uses env-var overrides as the correction path.cargo test — all pass (new resolver unit tests + updated provider/ground tests).cargo clippy --all-targets — clean.cargo fmt --check — clean.tsc --noEmit — clean (only pre-existing bun:test import errors remain).dist/ rebuilt.https://claude.ai/code/session_013mv3epyceLahz9Cue1mtPj
Generated by Claude Code
Originally posted by: Akarsh-Hegde
Review comments addressed + conflicts resolved
HIGH —
build_dataset.pystatus_category referencesAlready fixed in commit
41b8746(fix(pm): update eval dataset builder for dynamic status) — that commit replacesstatus_categorywithstatus_rawin the SELECT andLOWER(status_category) != 'done'withis_terminal = 0in the WHERE. Nothing further needed there.MEDIUM — Keyword heuristic substring false positives
Fixed in
92b8901(fix(status): word-boundary keyword matching + once_cell env cache).Switched
heuristic_terminalfromlower.contains(kw)to word-boundary matching — splits the raw status name on non-alphanumeric characters and checks if any word exactly equals a keyword:"Incomplete"→ words:["incomplete"]→ no match (was: hits "complete") ✓"Uncancelled"→ words:["uncancelled"]→ no match (was: hits "cancel") ✓"Complete","Cancelled","Canceled"→ still classified terminal ✓Also expanded the keyword list to include
"completed","cancelled","canceled"so their explicit forms still match after removing the substring shortcut.Both
"Incomplete"and"Uncancelled"added to theheuristic_leaves_open_columns_opentest.LOW —
env::var()uncachedFixed in the same commit. Replaced the two
env_list_containscalls with acached_env_lists(key)function backed by aLazy<RwLock<HashMap<String, (Vec<String>, Vec<String>)>>>. Both terminal and open lists for a provider are read once per process lifetime (2 syscalls total, regardless of task count). Addedclear_env_cache()under#[cfg(test)]and call it at the start of each env-manipulation test to prevent stale cache entries from bleeding between test cases.Conflicts
Conflicted with PR [#258] (
due_date/start_dateadditions now on main) injira.rs,linear.rs, andtasks/route.ts. Resolved by merging both sets of changes:jira.rs:status_raw + is_terminal + due_datein column list (14 cols, 13 ?-placeholders +strftime) — also fixed the extra?count mismatch that existed in main's linear upsert before this PRlinear.rs:status_raw + is_terminal + due_date(15 cols, 13 ?-placeholders +''for issue_type +strftime)tasks/route.ts: SELECT now includesstatus_raw, is_terminal, parent_key, epic_title, due_date, start_dateAll 9 status unit tests pass after the changes.
Related
Tickets:
#258Originally posted by: Akarsh-Hegde
Pushed
82e352dto fix the red CI after the [#258] merge — three things:035_pm_tasks_dates.sqlon main while this PR added035_pm_tasks_dynamic_status.sql— duplicate version35trippedUNIQUE constraint failed: _sqlx_migrations.versionand failed every DB-backed test. Renumbered this PR's migration to036_pm_tasks_dynamic_status.sql(the two add independent columns, so order doesn't matter). Heads-up in case you have it referenced as 035 locally.cargo fmton the reworked keyword list / test arrays instatus.rs.EnvListstype alias to satisfyclippy::type_complexity.Full suite green locally (259 + integration). The word-boundary heuristic and env-cache changes look good — nice catch on the
"Incomplete"/"Uncancelled"substring false positives.Generated by Claude Code
Related
Tickets:
#258Ticket changed by: Akarsh-Hegde
Originally posted by: adityaharishch
🎉 This PR is included in version 1.48.2 🎉
The release is available on:
v1.48.2Your semantic-release bot 📦🚀