Skip to content

VC-796: host-owned project.remove stops a project's live work before its delete - #920

Merged
hussainph merged 3 commits into
mainfrom
volli/VC-796-removing-a-project-cascades-deletes-its-sessions
Oct 19, 2026
Merged

hussainph merged 3 commits into
mainfrom
volli/VC-796-removing-a-project-cascades-deletes-its-sessions

Conversation

@hussainph

@hussainph hussainph commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Base update: #884 and #895, which this branch was stacked on, have both merged into main, so the diff against main is now this one commit.

Defect

volli:project-remove (apps/desktop/src/main/data-ipc.ts) ran deleteProject(db, id), a bare DELETE FROM projects. With foreign keys on, that delete cascades: sessions.project_id and session_events.session_id are both ON DELETE CASCADE, so it removed every Session of the project along with its event history. The work those Sessions held stayed live:

  • structured agent bindings, so a running turn kept spending and acting in the folder, and every later append failed the foreign key;
  • background shells;
  • the PTYs of other windows;
  • Browser Tabs.

The only teardown ran in the renderer after the delete (stores/projects.ts), and it only killed PTYs. No data-changed was published, so a second window kept showing the project.

Fix

project.remove is a new DESKTOP_ENTRIES key (host-command, workspace placement, matching the channel's VC-574 placement) with a handler-map entry. The handler runs removeProject (packages/host-core/src/project-remove.ts) in this order:

  1. Stop the live work of every Session the cascade would take (read from sessions.project_id):
    • release the structured bindings through the existing release-agent-sites path; releaseAgentSessions is a session-id sibling of releaseAgentSites, because ticket worktrees need not sit under the project folder;
    • BackgroundShellHost.disposeSession for each Session;
    • new PtyManager.killSessions, whichever window owns the PTY;
    • close every Browser Tab of the project;
    • new McpSettingsService.disconnectProject, which cancels in-flight MCP sign-ins and names the servers. Each Session's own MCP connections close when its binding is released.
  2. Delete the project row.
  3. McpSettingsService.forgetRemovedServers: calls McpCredentialStore.delete for every cascaded server, but only once its row is gone.
  4. End the board feed.
  5. Publish an untargeted data-changed to every window. It goes through feed.untapped so the disposed feed is not re-stamped. The renderer's untargeted refresh re-reads the project list, so a second window drops the project.

hostProjectLiveWork builds those ports from a host's services. Desktop main wires it in createHostHandlers; onProjectRemoved is gone. volli:project-remove now just invokes handlers["project.remove"] under the desktop window's policy.

Renderer:

  • stores/projects.ts no longer kills PTYs. It calls forgetProjectSessions, which only disposes terminal engines and drops records.
  • The remove dialog's busy list now also counts structured Sessions with a turn open and running background shells (remove-project-busy.ts).

How the regression tests prove it

  • apps/desktop/src/main/data-ipc.test.ts › project removal
    • Every stop port asserts the project row still exists when it runs, and the test checks the project is gone and data-changed reached the windows.
    • A binding that will not close keeps the project and answers { ok: false }.
    • Both fail on main: I checked by reverting data-ipc.ts and stores/projects.ts on this branch. Nothing is stopped and no data-changed is sent.
  • packages/host-core/src/project-remove.test.ts (enrolled at 100% coverage):
    • Order is pinned step by step together with what the database held at each step: bindings → shells → terminals → tabs → MCP disconnect run while the project and Session rows exist; MCP forget → feed dispose → announce run after the cascade.
    • Refusals cover a binding that stays open and a Session started during the release.
    • End to end over hostProjectLiveWork, a turn open in a ticket worktree outside the project folder is released by Session id.
  • mcp/settings.test.ts: credential rows are gone only after the delete, and another project's are kept.
  • pty/manager.stream.test.ts: killSessions kills exactly the named PTYs across windows.
  • stores/projects.test.ts: removal no longer calls terminal.kill.
  • session-lifecycle.test.ts: forgetProjectSessions disposes and forgets but kills nothing.

Decisions

  • A Session that will not stop refuses the removal; this was the conservative option. If a binding is still open after the release, or a Session started in the project while the release awaited, nothing else is stopped and nothing is deleted. The person sees "A Session in this project is still running, so nothing was removed. Stop it and try again." The worktree paths stay best-effort, but here deleting history under a live turn is the defect itself, so keeping the data won.
  • A host with no live-work port answers unavailable rather than deleting. That is hostd today, which serves no desktop entries.
  • Every Browser Tab of the project is closed, the person's own included. Nothing in the product can show a tab of a removed project.
  • Release origin is { kind: "user" }: removing the project was the person's act. No new SessionOrigin reason was added.
  • data-changed is untargeted. A targeted change for a project a window holds only re-reads that project's roster, which would not drop it.
  • volli:project-remove stays a channel that projects the handler, as the ticket proposed. It is not moved onto the RPC client.

Not in this PR

  • The cascade semantics and the dialog's "The folder on disk is untouched." text are unchanged. Whether removal should delete Session history at all is the owner's call (VC-822, which will stack on this).
  • Session-transcript artifacts and next_ticket_number after a re-add (VC-822's list).
  • Other windows' terminal tabs for the removed project. Their PTYs are killed on the host, but each window's sessions store keeps its records until it forgets the project.

Checks run locally

  • vp check: pass.
  • Typecheck of @volli/shared, @volli/host-core, @volli/session-rpc, @volli/desktop, @volli/hostd, plus the root tsc -p tsconfig.json: pass.
  • pnpm check:protocol-schema --base origin/main: additive check passed; the schema was regenerated with the generator.
  • Focused tests, all passing:
    • shared desktop-entries, handler-keys;
    • session-rpc desktop-router, desktop-ipc, handler-projection, protocol-schema, session-handlers.test-support;
    • host-core project-remove, host-handlers, mcp/settings, pty/manager.stream, worktree/agent-sites;
    • desktop data-ipc, session-lifecycle, stores/projects, remove-project-busy, ipc/placement.
  • Coverage at 100% for project-remove.ts, host-handlers.ts, session-lifecycle.ts and desktop-router.ts, each checked alone.
  • Not run here: the live app with a scripted runtime. The acceptance case is covered at the host-core and IPC layers.

CI history

…its delete

Removing a project deleted its row (and, by cascade, every Session and its
event history) while the Sessions' structured bindings, background shells,
terminals and Browser Tabs stayed live; the only teardown ran in the
renderer, after the delete, and killed terminal PTYs alone.

project.remove (a DESKTOP_ENTRIES key and a handler-map entry) now releases
the project's Sessions' bindings, disposes their shells, kills their
terminals, closes the project's tabs and cancels its MCP sign-ins; then
deletes; then forgets the cascaded MCP servers' credentials, ends the board
feed and publishes data-changed to every window. volli:project-remove is its
projection, and the renderer only forgets its own views.
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: ad4ae83e-cdbf-4ecd-b22c-966f5d2dc5c8
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hussainph hussainph changed the title VC-796: host-owned project.remove stops a project's live work before its delete (stacked on #895 — merge after it) VC-796: host-owned project.remove stops a project's live work before its delete Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant