Skip to content

POST /api/sessions with a soft-deleted id writes an invisible row, then 500s#2279

Description

@QuentinBisson

馃搵 Prerequisites

Describe the bug

SessionService.CreateSession with the id of a soft-deleted session writes a row that no read path can see, damages the tombstone on its way, and then fails with an internal error. The write is not rolled back, and every retry behaves the same.

  • SoftDeleteSession sets deleted_at on the session row, which keeps its (id, user_id) primary key.
  • UpsertSession upserts with ON CONFLICT (id, user_id) DO UPDATE SET name, agent_id, source, updated_at, so deleted_at stays set and the tombstone's name, agent_id and source are overwritten with the new request's values.
  • Every read (GetSession, ListSessions, ...) filters deleted_at IS NULL.

So the create path's own reload cannot see the row it just wrote, and the RPC fails codes.Internal "Failed to load created session". The caller is told the request failed, while a write did happen, to the row an audit of the deleted session would read. A request should either create a visible session or change nothing.

Proposed fix

Reject the create with codes.AlreadyExists when the (id, user_id) belongs to a soft-deleted session, and do not write. Excluding soft-deleted rows from the ON CONFLICT update is enough and keeps it atomic; a pre-check in the service leaves a window between the read and the write. The tombstone, its events and its tasks are left exactly as they are.

This is deliberately not a proposal to reuse the id: see #2331, where that was rejected, correctly. A distinct code also lets a caller tell "this id is retired, use another" from "kagent is unhealthy, retry", which an internal error does not.

Steps to reproduce

  1. CreateSession with an explicit id.
  2. DeleteSession with that id.
  3. CreateSession with the same id again: codes.Internal "Failed to load created session", with a row written that no read can see and a tombstone whose name is now the second request's. Every retry behaves the same.

Reproducible directly against the store: StoreSession, DeleteSession, StoreSession returns no error, the following GetSession errors, and SELECT name FROM session shows the second value.

Observed on

kagent 0.9.9; code paths verified identical on v0.10.0-beta7 and reproduced against main at 30e056d.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions