ubiquitous-invention/plans/Plan-agent-coordination/Epic-task-as-runnable-unit/Task-deep-link-runs-to-task-detail.md

80 lines
5.4 KiB
Markdown
Raw Normal View History

---
kind: task
slug: deep-link-runs-to-task-detail
title: Deep-link agent runs to task detail + document MCP stdio staleness
plan_slug: agent-coordination
epic_slug: task-as-runnable-unit
status: done
priority: P2
tenant_id: global
owner: unassigned
cursor_todo_id: null
updated_at: "2026-06-03"
---
# Task summary
Two small follow-ups from `Task-workflow-prompt-task-detail-ui` so the editor surface is actually reachable, and from `Task-export-db-to-markdown-frontmatter` so the next operator doesn't spend an hour debugging a stale MCP process.
## Description
### (1) Linkify `/settings/runs` rows → task detail page
Today the task title cell in `/settings/runs` is plain text. The route I just shipped lives at `/[workspaceSlug]/plans/[planSlug]/[epicSlug]/[taskSlug]` but you can only reach it by typing the URL. Closing this gap unlocks the dogfood loop: claim a task via MCP → run appears in `/settings/runs` → click through to inspect / edit the workflow prompt.
Concretely:
- `runs.listRecent` and `runs.listForTask` already join `markdown_backlog_items`; add `planSlug`, `epicSlug`, and `slug` to the selects.
- In `app/(app)/[workspaceSlug]/settings/runs/page.tsx`, wrap the task title cell in a `next/link` `<Link>` when the joined row has all three slugs and `taskKind === "task"`. Plans/epics don't have a detail page yet, so render those titles unlinked.
- Stop the link click from also toggling the row's expand/collapse handler (e.g. `e.stopPropagation()`).
### (2) Document MCP stdio process staleness
Symptom: `complete_task` returned without an `export` field and the on-disk `.md` file stayed `status: draft` even though the DB row flipped to `done`. Direct calls to `exportBacklogItemToMarkdown` work fine.
Root cause: the MCP server is launched by Cursor as `tsx --env-file=../../.env src/index.ts` (NOT `tsx watch`) and lives for the duration of the chat session. `tsx` imports the module graph once at startup; subsequent edits to `apps/mcp-server/**/*.ts` or any imported library (`packages/database/src/markdown-backlog/**`) don't take effect until the process is killed and Cursor reconnects. Worse, multiple stale MCP processes can accumulate from prior sessions; if Cursor binds the new session to one of those, you'll be talking to even older code.
This is a process-lifecycle gotcha, not a code bug — but it bit me right after we shipped the export feature, which means the next operator will hit it too.
Document in `config/CursorSync.md` under a new "MCP server lifecycle" section:
- The server is spawned once per Cursor chat session via `.cursor/mcp.json`.
- Changes to `apps/mcp-server/**` or any imported `packages/database/**` library require restarting the Cursor session (or killing the stale `tsx ... src/index.ts` process and starting a new chat).
- How to find and kill stale processes: `pgrep -fa "apps/mcp-server.*src/index.ts"`.
- Why we don't run `tsx watch`: a process restart would drop the stdio JSON-RPC connection mid-call.
Also add a one-liner pointer from `AGENTS.md` (the dev-loop section) so it's discoverable without grepping.
Out of scope for this task: actually fixing the staleness (would need a graceful-reload protocol or a switch to `tsx watch` with reconnection logic in the client). Documenting is the cheap, correct first step.
## Subtasks
- [x] Add `planSlug`/`epicSlug`/`slug` to `runs.listRecent` and `runs.listForTask` selects.
- [x] Wrap the task title cell in `/settings/runs` in a `Link` when all three slugs are present and `taskKind === "task"`.
- [x] Update `config/CursorSync.md` with an "MCP server lifecycle" section.
- [x] Add a one-line pointer in `AGENTS.md`.
## Acceptance criteria
- [x] Clicking the task title in `/settings/runs` navigates to the task detail page (and does NOT also toggle the row's expand/collapse).
- [x] `pnpm lint && pnpm type-check && pnpm test` clean.
- [x] `config/CursorSync.md` explains MCP staleness and how to recover, and `AGENTS.md` points at it.
## Implementation notes
- Selects: added `taskSlug` / `taskPlanSlug` / `taskEpicSlug` to both `runs.listRecent` and `runs.listForTask` (same shape on both for client-side reuse).
- Link wiring: new `buildTaskHref()` helper in the runs page returns `null` for plans/epics, for rows missing any path segment, and during the initial render before `workspaceSlug` is known. The cell renders `row.taskTitle` unlinked in those cases so the table doesn't flicker between linked/unlinked.
- Click stop: `onClick={(e) => e.stopPropagation()}` on the `<Link>` so the surrounding row's expand/collapse handler doesn't fire on link clicks. Verified via the AC.
- One real surprise during verification: `pnpm type-check` failed against a stale `.next/types/...` generated file (Next.js's per-page type validator was tripping on an unrelated pre-existing `export function formatDuration` in the runs page). Removing `apps/web/.next/types` and re-running cleared it. Logged because the next operator may hit it after the dev server has been running across a refactor.
## Follow-ups
- The exported helper `formatDuration` in the runs page should either be moved to a shared util (it's now informally part of the runs-row contract) or have its `export` keyword dropped. Out of scope here.
- Linking plan/epic titles from the runs page once a plans-tree browser exists.
## Links
- Parent surface: `./Task-workflow-prompt-task-detail-ui.md`
- Sync contract: `../../config/CursorSync.md`
- Epic: `./Epic-task-as-runnable-unit.md`