Skip to content

fix(server): report not found when removing an unknown shell - #56809

Closed
reynold-hu wants to merge 1 commit into
anomalyco:devfrom
reynold-hu:shell-remove-not-found
Closed

reynold-hu wants to merge 1 commit into
anomalyco:devfrom
reynold-hu:shell-remove-not-found

Conversation

@reynold-hu

@reynold-hu reynold-hu commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Part of #56804. This fixes the API contract that makes a stale shell undetectable; settling running tool rows when a Location is torn down is a separate change.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

DELETE /api/shell/:id silently succeeded for an unknown id: the handler caught Shell.NotFoundError and returned 204, while GET /api/shell/:id already returns 404. The silent success is why a client cannot tell that a shell vanished (for example after its Location was evicted), so a stale "running" item cannot be detected or cleared through this route.

The handler now maps the missing shell to the declared ShellNotFoundError (404), and the endpoint declares that error so the response is part of the public API instead of surfacing as an internal error.

How did you verify your code works?

  • New packages/server/test/shell.test.ts: DELETE of an unknown sh_… id returns 404 with _tag: "ShellNotFoundError", while GET keeps returning 404. On the parent commit the same request returns 204.
  • Full packages/server suite: 79 pass, 6 skip, 0 fail.
  • Regenerated the OpenAPI and client artifacts with the documented scripts; check:generated passes for packages/protocol and services/www.
  • packages/server and packages/protocol typecheck pass.

Screenshots / recordings

No UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #56804

Public API: DELETE /api/shell/{id} for an unknown id returns 404 ShellNotFoundError instead of 204 (not backwards compatible)

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does what it says: the new test fails on the base (DELETE returns 204) and passes here, and typecheck passes for packages/server and packages/protocol. But I don't think it should go in as it is.

Public API: DELETE /api/shell/{id} on an unknown or already removed id changes from 204 to 404 with ShellNotFoundError, and that error is added to the endpoint's 404 response in the OpenAPI spec. This is not backwards compatible. Callers that rely on removing a shell twice safely will now get an error.

Concerns:

  1. The 204 was an intentional API decision. b1f6d21ef ("refactor(protocol): refine vcs and shell APIs") replaced this same 404 mapping with a no-op, and V2_HTTP_API_AUDIT.md (entry 135) says: "Shell deletion is idempotent and returns 204 when already absent." A change to that contract needs a maintainer's agreement on #56804 first, and the audit entry should change with it.
  2. It doesn't clear the stuck item from #56804. The ghost "1 running" entry comes from client and session state that never gets settled. With this PR the app's stop action shows a "Request failed: Shell command not found" toast, but the item stays. Settling that state (the follow-up you mention) is what fixes the user-facing problem. Once it lands, the stop action no longer needs a 404.
  3. New error cases for existing callers. A shell that exits just before the user clicks stop now gives an error toast in the app. The TUI kill action (packages/tui/src/routes/session/composer/shell-tab.tsx) calls void client.api.shell.remove(...) without a .catch, so a 404 there becomes an unhandled promise rejection. If the API does change, these callers should treat "not found" as "already stopped".

Related: #56804

Effect.fn(function* (ctx) {
const shell = yield* Shell.Service
yield* shell.remove(ctx.params.id).pipe(Effect.catchTag("Shell.NotFoundError", () => Effect.void))
yield* shell.remove(ctx.params.id).pipe(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reverses b1f6d21ef, which removed this mapping on purpose: V2_HTTP_API_AUDIT.md entry 135 says shell deletion returns 204 when the shell is already absent. If the contract should change, please get a maintainer's agreement first, update the audit entry, and make the TUI and app callers treat a 404 as "already stopped". As it stands, the app shows an error toast and the stale item stays.

@reynold-hu

Copy link
Copy Markdown
Contributor Author

Closing per the review: the 204 on an absent shell is an intentional contract (V2_HTTP_API_AUDIT entry 135, b1f6d21), and this change alone would not clear the stuck entry. The user-facing fix belongs in settling session/timeline state when a Location is torn down, which is the remaining part of #56804. Thanks for the detailed check.

@reynold-hu reynold-hu closed this Oct 19, 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