siyuan-note/siyuan · error
agent session has an uncommitted turn
Error message
agent session has an uncommitted turn
What it means
beginRuntimeTurn refuses to install a new ActiveTurn when the session already has a different ActiveTurn that has not been committed, raising 'agent session has an uncommitted turn'. The runtime serializes turns per session: only one uncommitted turn may be in flight. Once the existing turn is committed, it is cleared and the new turn proceeds.
Source
Thrown at kernel/agent/runtime.go:272
return fmt.Errorf("invalid session id")
}
if turn.TurnID == "" || turn.State != "running" {
return fmt.Errorf("invalid agent runtime turn")
}
lock := sessionLock(sessionID)
lock.Lock()
defer lock.Unlock()
runtime, err := loadRuntimeLocked(sessionID)
if err != nil {
return err
}
if runtime.ActiveTurn != nil && runtime.ActiveTurn.TurnID != turn.TurnID {
committed, err := isTurnCommittedLocked(sessionID, runtime.ActiveTurn.TurnID)
if err != nil {
return err
}
if !committed {
return fmt.Errorf("agent session has an uncommitted turn")
}
runtime.ActiveTurn = nil
}
data, err := os.ReadFile(filepath.Join(sessionsDir(), sessionID, "session.json"))
if err != nil {
return err
}
var session map[string]any
if err := gulu.JSON.UnmarshalJSON(data, &session); err != nil {
return err
}
if turn.BaseRevision >= 0 && numberToInt64(session["revision"]) != turn.BaseRevision {
return ErrSessionConflict
}
if findRuntimeUserAnchor(session, turn.UserEntryID) < 0 {
return fmt.Errorf("agent runtime user entry not found")
}
runtime.ActiveTurn = turnView on GitHub (pinned to 8641553a1f)
Solutions
- Commit or roll back the existing ActiveTurn (via the session save/commit path) before starting a new turn
- Check the session's runtime state before beginning a turn and reject/queue if an uncommitted turn exists
- Serialize turn start per session in the caller (e.g. per-session queue or mutex)
Example fix
// before
beginRuntimeTurn(sid, turnB) // fails: turnA uncommitted
// after
if err := commitRuntimeTurn(sid, turnA); err != nil { return err }
beginRuntimeTurn(sid, turnB) Defensive patterns
Strategy: try-catch
Validate before calling
if runtime.ActiveTurn != nil && runtime.ActiveTurn.TurnID != newTurn.TurnID { ensureCommitted(sessionID, runtime.ActiveTurn.TurnID) } Try / catch
if err := beginRuntimeTurn(sid, turn); err != nil && strings.Contains(err.Error(), "uncommitted turn") { commitOrRollbackActiveTurn(sid); return beginRuntimeTurn(sid, turn) } Prevention
- Complete or roll back each turn before starting the next
- Queue turn starts per session
- Handle crash recovery to clear stale ActiveTurns
When it happens
Trigger: Calling beginRuntimeTurn while a previous turn is still active and isTurnCommittedLocked returns false for it (same sessionID, different TurnID than the incoming turn).
Common situations: Concurrent or overlapping agent requests on the same session; a previous turn crashed without commit/rollback; a client retried before the first request finished.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- invalid agent runtime turn
- agent runtime turn changed
- agent session revision conflict
- agent runtime turn is not finalized
- agent runtime user entry not found
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/15e7a627317d1004.
Report an issue: GitHub.