feat(linear): support custom workflow statuses#1364
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Requesting changes: the Linear wizard no longer exposes the existing friction status slot once workflow statuses load, which regresses friction reporting setup.
Code Issues
Should Fix
- web/src/components/projects/pm-providers/linear/wizard.ts:184 — Switching the Linear mapping UI to
workflowStatuses.listdrops thefrictionslot becauseBUILTIN_WORKFLOW_STATUSESdoes not include it.statuses.frictionis still part of the Linear config contract and is required by friction materialization, so new Linear projects can no longer configure friction reports through the wizard.
🕵️ codex · gpt-5.5 · run details
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Requesting changes because custom Linear statuses can dispatch but do not participate in lifecycle moves, and Linear setup can auto-enable backlog-manager unexpectedly.
Code Issues
Should Fix
- src/pm/linear/integration.ts:92 - Custom workflow statuses saved in
linearConfig.statusesare not included in the normalized lifecycle config. A custom agent can declarelifecycle.moveOnSuccess: storybecause the schema accepts arbitrary strings, butPMLifecycleManager.handleSuccess()resolves moves throughpmConfig.statuses[moveOnSuccess]; forstorythat lookup is undefined, sosafeMoveskips the transition. Any PRD/story/phased-plan style flow that relies on the normal lifecycle will dispatch when a user manually moves into the custom state, but it will not advance to the next custom Linear state on success. Include custom status mappings inresolveLifecycleConfigand widenProjectPMConfig.statusesaccordingly. - web/src/components/projects/pm-providers/save-trigger-configs.ts:30 - Linear setup now builds trigger configs for every mapped workflow status with an agent type. Since
BUILTIN_WORKFLOW_STATUSESgivesbacklogagentType: 'backlog-manager', a project that maps Backlog during Linear setup gets a DB override enablingbacklog-managerforpm:status-changed, even though that trigger's YAML default is false and the Trello/JIRA setup paths still only enable implementation/splitting/planning. After that, moving any Linear issue into Backlog can fire backlog-manager and pull work forward unexpectedly. Filter default-disabled built-ins such as backlog-manager out of the auto-created configs, or make the allowlist explicit.
🕵️ codex · gpt-5.5 · run details
Linear wizard now sources cascade stages from workflowStatuses.list, but BUILTIN_WORKFLOW_STATUSES omitted `friction` — so new Linear setups lost the UI row for `statuses.friction`, which friction materialization still requires. Add `friction` as a builtin (sortOrder 100, no agent) alongside `alerts` so the slot reappears in the wizard.
|
Updated the PR after the latest review pass. Changes pushed in
Verification:
|
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Requesting changes: manual/retry runs now always use PM lifecycle defaults, which regresses PR-based agent retries by mutating linked PM work items.
Code Issues
Should Fix
- src/triggers/shared/manual-runner.ts:152 — PR-based manual/retry runs now enter
runAgentExecutionPipelinewithout the GitHub execution config. The normal GitHub path explicitly setsskipPrepareForAgent,skipHandleFailure, andhandleSuccessOnlyForAgentTypeso review/respond-to-ci/respond-to-review runs do not add/remove PM lifecycle labels or post PM failure comments. A retry of a linked review run can now resolve the work item from the PR, callprepareForAgent, thenhandleSuccess, and mark the PM item processing/processed even though no implementation lifecycle occurred. Preserve the GitHub execution config for PR-based manual/retry runs, or scope this new lifecycle behavior to the PM/implementation retry case.
🕵️ codex · gpt-5.5 · run details
|
Fixed the latest manual/retry lifecycle review issue in Changes:
Verification:
Note: pre-push |
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Requesting changes because a custom workflow status can be mapped to an agent that the trigger resolver will never enable, making the core Linear custom-status dispatch path silently no-op.
Code Issues
Should Fix
- src/api/routers/workflowStatuses.ts:54 —
validateAgentTypeonly checks that the agent definition exists. A valid custom definition can still have nopm:status-changedtrigger; when a Linear issue enters the mapped state,LinearStatusChangedTriggerresolves that agent butcheckTriggerEnabledWithParams()delegates togetResolvedTriggerConfig(), which returnsnullif the definition does not declare the event, even if the wizard inserted anagent_trigger_configsrow. The webhook is then skipped. Reject these mappings with a clear error, or make workflow-status dispatch explicitly synthesize/honor status-trigger configs for mapped agents.
🕵️ codex · gpt-5.5 · run details
| async function validateAgentType(agentType: string | null | undefined) { | ||
| if (!agentType) return; | ||
| try { | ||
| await resolveAgentDefinition(agentType); |
There was a problem hiding this comment.
This accepts any existing agent as a workflow-status dispatch target, but the status trigger later goes through checkTriggerEnabledWithParams()/getResolvedTriggerConfig(), which returns null unless that agent definition declares pm:status-changed. A custom agent with triggers: [] is valid and passes this check, so the UI/CLI can save the mapping and even upsert a trigger config, but Linear status changes will silently skip it. Please either validate that the mapped agent supports pm:status-changed here or make the workflow-status dispatch path honor these mappings without requiring a declared trigger.
PR 1364 Workflow Status AuditPR: #1364 This document describes the intended behavior for custom Linear workflow statuses, SummaryPR 1364 introduces global workflow status definitions. Built-in statuses remain The critical runtime chain is:
Acceptance CriteriaAC1: Global workflow statuses merge built-ins and custom DB rows
Implementation path:
Verification:
AC2: Workflow status dispatch agents are runtime-compatible
Implementation path:
Verification:
AC3: Custom agent deletion does not leave stale workflow status mappings
Implementation path:
Verification:
AC4: Linear setup exposes custom statuses and creates safe trigger configs
Implementation path:
Verification:
AC5: Linear status-changed webhooks dispatch custom agents
Implementation path:
Verification:
AC6: Linear ready-label dispatch uses workflow definitions without changing semantics
Implementation path:
Verification:
AC7: Linear lifecycle moves support custom status keys
Implementation path:
Verification:
AC8: Manual and retry runs preserve the correct lifecycle semantics
Implementation path:
Verification:
AC9: Custom agent prompts and stats work with DB-backed agent types
Implementation path:
Verification:
Audit FindingsFixed locally before push
Intentional behavior to keep
Verification CommandsTargeted checks for this PR surface: npm test -- \
tests/unit/api/routers/workflowStatuses.test.ts \
tests/unit/api/routers/agentDefinitions.test.ts \
tests/unit/db/repositories/workflowStatusDefinitionsRepository.test.ts \
tests/unit/triggers/linear-status-changed.test.ts \
tests/unit/triggers/linear-label-added.test.ts \
tests/unit/triggers/shared/pm-status.test.ts \
tests/unit/triggers/manual-runner.test.ts \
tests/unit/triggers/agent-execution.test.ts \
tests/unit/triggers/github-integration.test.ts \
tests/unit/pm/lifecycle.test.ts \
tests/unit/pm/linear/integration.test.ts \
tests/unit/pm/lifecycle-config-shape.test.ts \
tests/unit/web/pm-wizard-hooks.test.ts \
tests/unit/web/global-definitions-route.test.ts \
tests/unit/api/routers/prompts.test.ts \
tests/unit/web/stats-filters.test.tsTEST_DATABASE_URL=postgresql://cascade_test:cascade_test@127.0.0.1:5433/cascade_test \
npx vitest run --project integration \
tests/integration/db/workflowStatusDefinitionsRepository.test.tsnpm run typecheckKnown local caveat: full changed-suite pre-push previously reached 315/316 test |
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Requesting changes because the new coalesced-dispatch lock shortcut can still enqueue duplicate same-type work when a pending job exists for the same coalesce key.
Code Issues
Blocking
- src/router/webhook-trigger-outcomes.ts:193 — When any pending coalesced job exists for the work item, the code skips
checkDispatchLocks()entirely. That is safe only for the pending job's own in-memory lock; it also skips real active same-type work-item locks and agent-type concurrency limits, so a replacement delayed job can be scheduled while the same agent is already running.
🕵️ codex · gpt-5.5 · run details
| } | ||
|
|
||
| try { | ||
| const hasPendingForKey = await hasPendingCoalescedJob(result.coalesceKey); |
There was a problem hiding this comment.
This hasPendingForKey shortcut treats any pending coalesced job for the key as if it is the only lock that needs to be ignored, so it skips checkDispatchLocks() entirely. That also skips real active same-type and agent-concurrency locks. For example: an implementation run for TF-38 is already active, a delayed planning job exists for ats:TF-38, then the issue is moved back to a status mapped to implementation. hasPendingCoalescedJob() returns true, this branch skips the active implementation lock, and the replacement delayed implementation job is added; when it fires there is no later lock check. The supersede path needs to release or ignore only the pending job's own lock and still check for active/other concurrency before adding the replacement.
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Requesting changes for one remaining workflow-status compatibility gap.
Code Issues
Should Fix
- src/api/routers/agentDefinitions.ts:181 — The new compatibility guard is not applied on
agentDefinitions.create. This endpoint explicitly supports creating DB overrides for YAML-backed built-in agent types (isBuiltinAgentType(input.agentType)is passed through on create, and there is a unit test forimplementation). On a fresh DB, a superadmin can create animplementationoverride withtriggers: []; the built-intodo -> implementationworkflow status still exists, butcheckTriggerEnabledWithParams()later returns disabled/null because the definition no longer declarespm:status-changed, so Linear moves into Todo silently stop dispatching implementation. The update/reset paths now prevent this, but create needs the sameassertWorkflowStatusDispatchCompatibility(input.agentType, input.definition)check beforeupsertAgentDefinition.
🕵️ codex · gpt-5.5 · run details
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Requesting changes for a coalesced PM dispatch lock bug: the replacement path can clear the pending job's lock and then fail to schedule, leaving the still-pending job unlocked and allowing duplicate same-type dispatches.
Code Issues
Should Fix
- src/router/webhook-trigger-outcomes.ts:199 — The pending job's locks are released before the replacement job is guaranteed to be scheduled. If
scheduleCoalescedJob()throws before removing the old BullMQ job, the catch path does not restore those locks, so the old delayed job remains queued without its in-memory work-item/agent-type protection and a later webhook can enqueue a duplicate run.
🕵️ codex · gpt-5.5 · run details
| projectId: project.id, | ||
| result: result as TriggerResult & { agentType: string }, | ||
| }); | ||
| if (shouldIgnorePendingOwnLock) { |
There was a problem hiding this comment.
This releases the pending job's in-memory locks before scheduleCoalescedJob() has actually replaced the BullMQ job. If the later schedule call throws, for example Redis fails during its second getDelayed/getWaiting call before the old delayed job is removed, the catch path logs and returns without restoring the locks. The old pending job remains queued but no longer has the work-item/agent-type lock, so a follow-up webhook can pass checkDispatchLocks() and enqueue a duplicate same-type run for the same work item. Please restore the released pending locks on scheduling failure when the old job is still pending, or avoid mutating the lock maps until the replacement has succeeded.
Summary
Adds database-backed workflow status definitions and wires them through Linear project setup, custom agent dispatch, dashboard stats filters, prompt loading, and CLI management. This lets Linear statuses map to custom Cascade workflow stages and custom agents while preserving the built-in statuses as immutable defaults.
Also routes manual/retry runs through the normal execution lifecycle so successful implementation retries still create/link PRs and advance the work item lifecycle.
Screenshots
Custom statuses
Status mapping to agent
Custom agent triggers
Test Plan
npm test)npm run lint)npm run typecheck)Additional verification run locally:
npm run buildnpm run verifynpm run test:fastChecklist