## Summary
Follow-up after **#2164** and main’s **FN-8103 / FN-8104**
(Postgres-only data access / SQLite retirement).
Main already routes Quality through `AsyncQualityStore` +
`getAsyncLayer()`. This PR keeps the **review hardening** that was still
missing:
- **Project binding** — reject request `projectId` mismatches vs bound
AsyncDataLayer; all SQL uses bound project
- **`createRunIfNoActive`** — advisory lock so concurrent starts cannot
double-queue
- **Cancel-safe runner** — cancel slot registered before the running
write; pre-spawn cancel skips process
- **`finalizeRun`** — never overwrites a `cancelled` terminal status
- **Detached execute** — catch only execution failures; prune fail-soft
in `finally`
- Guardrail tests + Quality v2 plan doc
## Test plan
- [ ] Task QA loads under PostgreSQL (no SQLite/backend-mode error)
- [ ] Concurrent start for same task → 409 second start
- [ ] Cancel during start does not leave a live orphan process
- [ ] Cancelled run stays cancelled after process exit
- [ ] `pnpm --filter @fusion-plugin-examples/quality test` (37 tests)
The Help link is an <a class="btn"> and .btn never resets link
text-decoration, so the browser's default underline painted under the
icon+label box, reading as an overlap. Drop the icon (redundant next to
the "Help" label) and reset text-decoration on the shared
discord/help anchor-button selector. Also fold the Help button into the
same mobile 36px touch-target rule as Export/Import/Reset/Save so all
five footer buttons render at a consistent height.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Export/Import/Reset/Cancel/Save use .btn-sm, which the global mobile
touch-target rule deliberately excludes to keep dense table/card button
rows compact. That exclusion also caught the Settings footer, leaving
those buttons at ~24px tall on mobile — visibly smaller than the Help
and auth Test/Disable buttons on the same rail. Scope the 36px minimum
back to just this footer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
## Why
FN-7996 sat in a dispatch→park loop **all day** (06:06→16:35): its
`worktree` metadata pointed at recycled pool worktrees (`coral-badger`,
`grand-ridge` — the latter actually belonged to FN-8069), Plan Review
refused to start in the missing directory, and the task terminal-parked
`failed` every cycle while the planner overseer blindly retried.
Root-cause chain:
1. **`graphFailureValue()` couldn't read optional-group results.**
`runOptionalGroup` publishes context under the group id
(`node:plan-review:value`) and the unqualified template id, but the
failed node is recorded as the materialized
`plan-review::plan-review-step` — the lookup only understood `#` foreach
ids. FN-7977's provider-failure hold *did* classify this failure, but
its hold value was invisible to routing.
2. **No graph-failure router handled the `assertValidWorktreeSession`
refusal**, so it fell to the terminal sink, which parked the task and
*overwrote* `task.error` with a generic message — erasing the signature
the missing-worktree self-healing sweep (in-review-only anyway)
classifies on.
3. **Plan Review didn't need the worktree at all** — its spec is
store-injected (FN-7561) — yet it launched its reviewer in whatever
stale `task.worktree` said.
## What
- `handleGraphFailure` routes unusable-worktree node failures (any node,
any error key, `::`/`#` materialized ids) into the existing bounded
worktree-session recovery: clear stale worktree/branch/session metadata,
requeue to todo, budgeted by `worktreeSessionRetryCount`. An exhausted
budget still falls through to the visible terminal park for human
inspection.
- `graphFailureValue` resolves `group::template` ids (group value first
— it carries post-classification routing intent — then the unqualified
template value). Foreach `#` behavior unchanged.
- Plan Review falls back to the repo root when its recorded worktree is
missing on disk; other read-only gates intentionally keep failing fast
into the new recovery (silently retargeting them to root would review
the wrong tree).
- `recoverMissingWorktreeSessionStartFailure` returns its outcome so the
graph router can distinguish requeue from escalate-exhausted; existing
truthy callers unchanged.
## Symptom Verification
- **Original symptom:** graph-node session-start refusal → `Workflow
graph terminated with failure at node 'plan-review::plan-review-step'`,
task parked failed with stale metadata intact, no recovery.
- **Reproduction:** `graph-node-missing-worktree-recovery.test.ts`
drives `handleGraphFailure` with the exact FN-7996 result shape
(optional-group materialized id + `Refusing to start coding agent in
missing worktree` node error).
- **Assertion it is gone:** the task is requeued to `todo` with
`worktree`/`branch`/`sessionFile` cleared and retry budget incremented —
and is *not* marked `failed`; budget exhaustion still parks visibly.
## Surface Enumeration
- Optional-group template nodes (Plan Review — the repro), write-capable
review gates, and any custom graph node: covered by the
`handleGraphFailure` router (scans exact/materialized/unqualified
`:error` keys).
- Execute-seam session start: already covered by the pre-existing
recovery (unchanged, still passes).
- In-review / merge-active columns: already covered by self-healing
sweeps (unchanged).
- Paused / user-paused / deleted / done tasks: explicitly left to their
owning machinery (guard tests).
- Budget exhaustion: falls through to the visible terminal park (test).
## Testing
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/reliability-interactions/graph-node-missing-worktree-recovery.test.ts`
— 13 passed
- Adjacent suites (`worktree-incomplete-session-start`,
`executor-graph-requeue-gate`, `workflow-graph-optional-group`,
`executor-paused-abort-todo-benign`) — 78 passed
- `tsc --noEmit` on `@fusion/engine` — clean
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved recovery when workflow tasks encounter missing or recycled
worktrees.
* Automatically retries affected tasks with stale worktree details
cleared, up to the configured retry limit.
* Escalates tasks after recovery attempts are exhausted.
* Improved failure routing for optional workflow groups and template
instances.
* Plan Review now falls back to the repository root when its recorded
worktree is unavailable.
* **Tests**
* Added regression coverage for recovery, routing, retry limits, and
repository-root fallback behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Import picker now surfaces all open issues instead of a silent first-30
slice, and reliably closes linked/imported issues when their task reaches
Done.
Import:
- GitHubImportModal fetches up to 300 open issues in one request and pages
the result client-side at 30/page with Previous/Next controls, a page
indicator, and a truncation notice past the cap.
- NewTaskModal reference picker limit 30 -> 100.
- GitHubClient.listIssues pages the REST path (per_page loop until
limit/exhaustion; PR-filtering no longer stops paging early) and lifts the
gh path's 100 cap (gh --limit paginates internally); gh-CLI label filtering
fetches the full cap before client-side OR filtering.
Close-on-Done:
- GitHubTrackingReconciler.runSweep isolates its three reconcile passes so a
throw in one (e.g. the PG-backend deleted/archived pass) no longer starves
the done-task + source-issue passes. Previously all three shared one
try/catch with a silent swallow, so a single early throw disabled the whole
close-on-Done reconcile backstop on every sweep, leaving linked issues open.
Failures are now logged instead of hidden.
Tests: REST multi-page paging, PR-filter-doesn't-stop-paging, Prev/Next UI,
one-page (no controls), and per-pass isolation invariants.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore PostgreSQL-backed collision coverage for the built task-delegation extension.
- Seed an occupied task ID through the shared PostgreSQL TaskStore.
- Override the allocator once to exercise the real unique-violation error path.
- Assert that fn_delegate_task returns the structured task-ID collision error.
Files changed:
.../src/__tests__/extension-integration.test.ts | 53 +++++++++++++---------
1 file changed, 32 insertions(+), 21 deletions(-)
Fusion-Task-Id: FN-8100
Fusion-Task-Lineage: d26b825e-2abd-482e-899e-51c0db753176
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Target the named reset control in the workflow review-cap test.
- Select the Reset to default button by accessible name
- Prevent ambiguity when multiple row buttons are rendered
Files changed:
packages/dashboard/app/components/__tests__/WorkflowSettingsPanel.test.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Fusion-Task-Id: FN-8101
Fusion-Task-Lineage: e822941f-eab0-4d7a-a27b-53c152aac5de
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The settings scope banners ("shared across all your Fusion projects" /
"only affect this project") were removed from the UI — scope is conveyed by
the nav's globe/folder icons and "· Global/Project" labels — leaving
settings.scope.globalBanner/projectBanner dead in all six catalogs. Removed
from the authored source of truth (packages/i18n/locales) and regenerated
resources.d.ts.
Also ran the documented pnpm i18n:sync remediation for the pre-existing
parity gap: seeds the missing secondary-locale keys (autoTranslate*,
skipConfirmationDialogs*, taskDetail.plan approval/replan copy) as empty
strings for fallback-to-en and aligns plural key shapes
(resultCount_one/_other), turning pnpm i18n:status and the @fusion/i18n
parity tests green again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
67 pre-existing failures: the Advanced settings simplification (2026-07-11) hid
advanced-only nav sections and display:none'd low-frequency form-groups in Basic
mode, and these two suites were never opted in. They now set the
fusion:settings:show-advanced preference like settings-mobile/models-auth do.
Also repointed stale assertions at current UI: research nav labels are
"Research · Global" / "Research · Project" (not "Research Defaults"/"Research"),
the ntfy Advanced disclosure click is scoped to its <summary> (the nav now also
renders an "Advanced" group header), the research provider disclosure holds
Settings*Row primitives rather than form-groups, and the removed global-scope
prose banner is asserted via the nav scope icon instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FN-8037 extracted pending-image previews into PendingImagePreviews,
which renders the thumbnail as a decorative alt="" image inside a
button labelled 'Open image <name>'. Point the four stale
getByAltText assertions at that accessible open button instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Switch the mobile row alignment from centered to space-between and
widen the icons+Save group to the full row, so the workflow/steps and
option-chip rows share the exact same left/right edges as the bottom
row. Group gaps remain as minimum spacing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restore the isolated built-core barrel regression guard to the default CLI test lane.
- Hoist recompilation and PostgreSQL fixture setup outside timed test bodies.
- Inject the fixture store into the dynamic extension and retain text-budget assertions.
- Skip cleanly per test when a transitive dist artifact is unavailable.
- Remove the matching CLI quarantine exclusion and ledger entry.
Files changed:
.../src/__tests__/extension-dist-barrel.test.ts | 234 +++++++++++----------
packages/cli/vitest.config.ts | 8 +-
scripts/lib/test-quarantine.json | 5 -
3 files changed, 121 insertions(+), 126 deletions(-)
Fusion-Task-Id: FN-8093
Fusion-Task-Lineage: 352b3675-0579-43bc-acd9-6a11919ed646
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Move the CLI plugin-install persistence coverage to the shared PostgreSQL test harness.
- Replace SQLite central and local database assertions with PostgreSQL schema queries.
- Assert global installation and project-scoped state persistence without a local plugin store.
- Clean up temporary plugin fixtures after the PostgreSQL test.
Files changed:
packages/cli/src/commands/__tests__/plugin.test.ts | 120 +++++++++++----------
1 file changed, 64 insertions(+), 56 deletions(-)
Fusion-Task-Id: FN-8091
Fusion-Task-Lineage: 99b8d1da-54b7-4cd3-a086-9e4461a8aa06
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Align the responsive TaskDetailModal tab-order assertion with the rendered tab set.
- Include Terminal and Cost between Comments and Artifacts in the responsive tab list.
- Document why the regression expectation includes the always-available tabs.
Files changed:
.../__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx | 3 +++
1 file changed, 3 insertions(+)
Fusion-Task-Id: FN-8039
Fusion-Task-Lineage: 86f347a7-ca5a-4a83-8616-4ca303cc6115
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Keep GitLab import details consistent with the shared padded FloatingWindow layout.\n\n- Move the GitLab preview into the shared heading and scrolling-content structure\n- Place the GitLab import control in the shared bottom action bar for mobile reachability\n- Cover cross-provider detail layout and shared mobile-sheet invariants\n\nFiles changed:\n .../dashboard/app/components/GitHubImportModal.tsx | 20 ++++++++++----\n .../__tests__/GitHubImportModal.test.tsx | 32 +++++++++++++++++++---\n 2 files changed, 42 insertions(+), 10 deletions(-)
Fusion-Task-Id: FN-8032
Fusion-Task-Lineage: 150b992a-d5fa-4001-b818-7ece031a6cb5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>