Skip to content

V2: preserve permission and question waits across server restart #36347

Description

@kitlangton

Summary

Pending permission requests and question forms are process-local in V2. Restarting the server destroys the request and its suspended continuation. Managed-service restart continuity may resume the Session, but it cannot restore or accept a reply to the old blocker.

We need a durable model for interactive waits that covers both permissions and questions without blindly replaying the tool call that created them.

Current behavior

Permissions keep pending requests in an in-memory Map with a Deferred owned by the running tool fiber:

  • packages/core/src/permission.ts
  • layer finalization declines every pending request and clears the map

Questions use Form.Service, whose entries and Deferreds live in an in-memory cache:

  • packages/core/src/form.ts
  • layer finalization cancels every pending form
  • the built-in question tool waits on forms.ask(...) inside its tool execution

On managed-server shutdown, active Sessions are suspended for restart continuity. On startup, the runner resumes the Session and reconciles stale running tools as interrupted. The old permission/form request no longer exists, so clients cannot display or answer it.

This also gives shutdown the wrong domain meaning for forms: finalization publishes cancellation even though the user did not cancel the question.

Design pressure

Persisting the request payload alone is insufficient. The reply currently settles a process-local Deferred inside a tool call, and that continuation disappears with the process.

Permissions are the harder case because permission.assert(...) can be called from within tool execution. Re-entering the tool to reach the permission check may repeat work or side effects that happened before the check. Treating every interrupted tool as replayable is therefore unsafe.

The design should decide where the restartable boundary lives. Possibilities include:

  • Make interactive blockers durable workflow records whose terminal reply can settle the owning tool call without restoring the old JavaScript continuation.
  • Move authorization to an explicit boundary before the physical tool attempt, so an approved attempt can start safely after restart.
  • Define a resumable tool protocol for tools that can suspend on interactive input, while ordinary tools remain non-replayable.

Questions and permissions should share the same lifecycle concepts where possible, even if permission evaluation and form rendering remain separate APIs.

Required semantics

  • A pending permission or question survives a graceful managed-service restart and remains visible through the existing list APIs.
  • The original request ID remains replyable after restart.
  • Server shutdown does not record a user rejection or cancellation.
  • A reply settles the blocker exactly once and allows the owning Session to continue.
  • Recovery does not repeat provider work or tool side effects that may already have executed.
  • Saved permission rules remain distinct from one pending permission decision.
  • Reconnect hydration and live events converge on the same durable blocker state.
  • Location and Session ownership checks continue to prevent cross-location or cross-session replies.
  • The design explicitly states whether unexpected process death is supported or remains outside the first implementation.

Coverage

Add an integration test that:

  1. Starts a Session that blocks on a permission request or question form.
  2. Tears down the server/location graph while preserving the database.
  3. Builds a fresh graph and performs managed restart continuation.
  4. Verifies the same blocker is listed and replyable.
  5. Replies and verifies the Session continues without duplicate tool side effects.

Cover permission approval, permission rejection, question answer, and question cancellation.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.0bugSomething isn't workingcoreAnything pertaining to core functionality of the application (opencode server stuff)

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions