siyuan-note/siyuan · error

read agent runtime failed: %w

Error message

read agent runtime failed: %w

What it means

When a save carries a commitTurnID (or recoveryTurnID), SaveSessionState loads the per-session agent runtime state to verify and apply the finished turn (kernel/agent/session.go:384-387). If loadRuntimeLocked fails — the runtime state file is unreadable or corrupt — the commit is refused with 'read agent runtime failed' because committing without the runtime's authoritative turn content would lose data.

Source

Thrown at kernel/agent/session.go:387

				if _, ok := newData[k]; !ok {
					// messages 是已废弃的旧会话字段,不再带入新格式;其他未知字段原样保留,
					// 避免前后端版本不一致时擦除较新版本写入的数据。
					if k != "messages" && k != "expectedRevision" && k != "commitTurnID" &&
						k != "recoveryTurnID" && k != "recoveryState" && k != "recoveryRevision" && k != "agentRunning" {
						newData[k] = v
					}
				}
			}
		}
	} else if err != nil && !os.IsNotExist(err) {
		return 0, nil, fmt.Errorf("read session file failed: %w", err)
	} else if meta.ExpectedRevision != nil && *meta.ExpectedRevision != 0 {
		return 0, nil, ErrSessionConflict
	}
	if commitTurnID != "" {
		runtime, err := loadRuntimeLocked(meta.ID)
		if err != nil {
			return currentRevision, nil, fmt.Errorf("read agent runtime failed: %w", err)
		}
		if runtime.ActiveTurn != nil {
			if runtime.ActiveTurn.TurnID != commitTurnID {
				return currentRevision, nil, ErrSessionConflict
			}
			if !isRuntimeTurnTerminal(runtime.ActiveTurn) {
				return currentRevision, nil, ErrRuntimeNotFinalized
			}
			if err := applyRuntimeTurnToSessionLocked(newData, runtime.ActiveTurn); err != nil {
				return currentRevision, nil, err
			}
		} else if currentCommittedTurnID != commitTurnID {
			return currentRevision, nil, ErrSessionConflict
		}
	}

	newRevision := currentRevision + 1
	newData["revision"] = newRevision

View on GitHub (pinned to afa823b6b4)

Solutions

  1. GET /api/ai/agent/getSession first — its FinalizeOrphanedTurn pass (kernel/api/agent.go:441) can repair/finalize an orphaned runtime turn so the subsequent commit succeeds
  2. Inspect the session directory for a truncated runtime state file and restore it from backup
  3. If unrepairable, remove the session (removeSession) and start a new one — the turn's runtime content is the authoritative record and must not be hand-faked
  4. Check what crashed or interrupted the write (power, OOM-kill, sync tool) to avoid recurrence
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

catch (e) {
  if (/read agent runtime failed/.test(e?.data?.msg ?? '')) {
    // first try GET /api/ai/agent/getSession to trigger FinalizeOrphanedTurn repair,
    // then retry the commit with the same commitTurnID exactly once
  }
}

Prevention

When it happens

Trigger: POST /api/ai/agent/saveSession with commitTurnID while the session's runtime state file (alongside session.json in data/storage/ai/agent/sessions/<id>/) is corrupt, zero-length, or unreadable — typically after a crash between writing runtime state and session.json, or partial sync of the workspace.

Common situations: Kernel killed mid-turn so the runtime file was half-written; workspace synchronized externally and the runtime file conflicted; disk issues. Often accompanied by the same session recovering oddly on next getSession (which runs FinalizeOrphanedTurn).

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/bd4dcf633093b581. Report an issue: GitHub.