siyuan-note/siyuan · error
invalid agent runtime turn state
Error message
invalid agent runtime turn state
What it means
Rejected by every siyuan.storage.* method (get/put/remove/list/watcher.add) when the resolved absolute path escapes the plugin's private storage directory (kernel/plugin/api_storage.go:43). resolvePath joins and cleans the input, then verifies the result is the storage dir itself or a child of it; '../' segments or absolute paths that clean to somewhere else fail this check. This is a deliberate sandbox escape prevention, so no error detail beyond the message is provided.
Source
Thrown at kernel/agent/runtime.go:219
return nil, err
}
if runtime.SchemaVersion > 1 {
return nil, fmt.Errorf("unsupported agent runtime schema version: %d", runtime.SchemaVersion)
}
if runtime.SessionID != "" && runtime.SessionID != sessionID {
return nil, fmt.Errorf("agent runtime session id mismatch")
}
if runtime.Revision < 0 {
return nil, fmt.Errorf("invalid agent runtime revision")
}
if runtime.ActiveTurn != nil {
if runtime.ActiveTurn.TurnID == "" {
return nil, fmt.Errorf("invalid agent runtime turn id")
}
switch runtime.ActiveTurn.State {
case "running", "finished", "interrupted":
default:
return nil, fmt.Errorf("invalid agent runtime turn state")
}
}
if runtime.SchemaVersion == 0 {
runtime.SchemaVersion = 1
}
if runtime.SessionID == "" {
runtime.SessionID = sessionID
}
return &runtime, nil
}
func writeRuntimeLocked(sessionID string, runtime *agentRuntime) error {
if runtime == nil {
return nil
}
// runtime 只能附着在已经存在的会话上,避免迟到的 checkpoint 复活已删除会话。
if _, err := os.Stat(filepath.Join(sessionsDir(), sessionID, "session.json")); err != nil {
return errView on GitHub (pinned to afa823b6b4)
Solutions
- Keep all storage keys relative to the plugin storage root and drop any leading '/' or '..' segments
- Sanitize user-supplied paths before use: strip '..' components or reject them
- For cross-plugin data, use siyuan.rpc or siyuan.event instead of filesystem traversal
- Normalize with a helper that returns null for escaping paths and prompt for a new one
Example fix
// before
await siyuan.storage.put(`../other-plugin/${name}`, data);
// after
const safeKey = name.split('/').filter(s => s && s !== '.' && s !== '..').join('/');
await siyuan.storage.put(safeKey, data); Defensive patterns
Strategy: validation
Validate before calling
const safeStorageKey = (p) => {
if (typeof p !== 'string') return null;
const parts = p.split(/[\\/]+/).filter((s) => s && s !== '.' && s !== '..');
return parts.length ? parts.join('/') : null;
};
const key = safeStorageKey(userPath);
if (key) await siyuan.storage.put(key, data); else throw new Error('invalid storage path'); Type guard
const isSafeStoragePath = (p) => typeof p === 'string' && p.length > 0 && !p.split(/[\\/]+/).includes('..') && !/^[a-zA-Z]:/.test(p) && !p.startsWith('\\\\'); Try / catch
try { await siyuan.storage.put(key, data); } catch (e) { if (/path traversal/.test(e.message)) throw new Error(`rejected unsafe storage path: ${key}`); else throw e; } Prevention
- Treat storage keys as relative identifiers, never filesystem paths
- Sanitize user input by stripping '..' segments before any storage call
- Use siyuan.rpc/siyuan.event for cross-plugin data instead of ../ paths
- Never feed absolute OS paths into siyuan.storage
When it happens
Trigger: put('../shared/config.json', data) trying to reach another plugin's storage; get('/etc/passwd') or any absolute path that is not under storageDir; watcher.add('..') ; paths like 'a/../../b' that clean outside the root after filepath.Clean collapses them.
Common situations: Sharing data between two plugins via relative ../ paths instead of a proper channel (rpc/event); user-typed paths pasted into plugin settings; path building with template strings like `${userInput}/notes` where userInput contains '..'; Windows drive-letter or UNC paths.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- agent runtime user entry not found
- import path is not sub path of import dir
- agent runtime session id mismatch
- invalid agent runtime revision
- invalid agent runtime turn id
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/469c7213e873dfab.
Report an issue: GitHub.