馃搵 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
CreateSession with an explicit id.
DeleteSession with that id.
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.
馃搵 Prerequisites
Describe the bug
SessionService.CreateSessionwith 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.SoftDeleteSessionsetsdeleted_aton the session row, which keeps its(id, user_id)primary key.UpsertSessionupserts withON CONFLICT (id, user_id) DO UPDATE SET name, agent_id, source, updated_at, sodeleted_atstays set and the tombstone'sname,agent_idandsourceare overwritten with the new request's values.GetSession,ListSessions, ...) filtersdeleted_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.AlreadyExistswhen the(id, user_id)belongs to a soft-deleted session, and do not write. Excluding soft-deleted rows from theON CONFLICTupdate 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
CreateSessionwith an explicitid.DeleteSessionwith that id.CreateSessionwith the same id again:codes.Internal"Failed to load created session", with a row written that no read can see and a tombstone whosenameis now the second request's. Every retry behaves the same.Reproducible directly against the store:
StoreSession,DeleteSession,StoreSessionreturns no error, the followingGetSessionerrors, andSELECT name FROM sessionshows the second value.Observed on
kagent 0.9.9; code paths verified identical on v0.10.0-beta7 and reproduced against
mainat 30e056d.