{"record":{"id":"6d80c824428b6b2c","repo":"siyuan-note/siyuan","slug":"save-session-file-failed-w","errorCode":null,"errorMessage":"save session file failed: %w","messagePattern":"save session file failed: %w","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"kernel/agent/session.go","lineNumber":418,"sourceCode":"\t\t\treturn currentRevision, nil, ErrSessionConflict\n\t\t}\n\t}\n\n\tnewRevision := currentRevision + 1\n\tnewData[\"revision\"] = newRevision\n\tif commitTurnID != \"\" {\n\t\tnewData[\"lastCommittedTurnID\"] = commitTurnID\n\t}\n\tdata, err = gulu.JSON.MarshalIndentJSON(newData, \"\", \"\\t\")\n\tif err != nil {\n\t\treturn currentRevision, nil, fmt.Errorf(\"encode session data failed: %w\", err)\n\t}\n\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn currentRevision, nil, fmt.Errorf(\"create session dir failed: %w\", err)\n\t}\n\tif err := filelock.WriteFile(path, data); err != nil {\n\t\treturn currentRevision, nil, fmt.Errorf(\"save session file failed: %w\", err)\n\t}\n\tif commitTurnID != \"\" {\n\t\tif err := markRuntimeCommittedLocked(meta.ID, commitTurnID); err != nil {\n\t\t\tlogging.LogWarnf(\"commit agent runtime failed: %s\", err)\n\t\t}\n\t}\n\n\ttitle, _ := newData[\"title\"].(string)\n\tif title == \"\" {\n\t\ttitle = \"AI Agent\"\n\t}\n\tcreatedAt := meta.CreatedAt\n\tif value := numberToInt64(newData[\"createdAt\"]); value > 0 {\n\t\tcreatedAt = value\n\t}\n\tupdatedAt := meta.UpdatedAt\n\tif value := numberToInt64(newData[\"updatedAt\"]); value > 0 {\n\t\tupdatedAt = value","sourceCodeStart":400,"sourceCodeEnd":436,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/agent/session.go#L400-L436","documentation":"The actual persistence step of SaveSessionState: filelock.WriteFile atomically writes session.json under the session directory (kernel/agent/session.go:417-418). Any failure is wrapped as 'save session file failed'. The directory already exists at this point, so failures are write-level: disk full, permission on the file itself, or the cross-process file lock being unavailable.","triggerScenarios":"POST /api/ai/agent/saveSession when the disk is full (ENOSPC), session.json is read-only or owned by another user (EACCES), a stale lock file blocks filelock on Windows, or the filesystem is transient (network volume dropped). Note the revision was already computed — the caller must re-load before retrying.","commonSituations":"Full disk during long agent sessions; antivirus/indexer holding the file on Windows; two SiYuan instances on the same workspace without the sync-detection noticing; flaky removable storage.","solutions":["Free disk space / fix ownership of session.json, then re-run the save from a freshly loaded session (revision may have advanced)","Delete a stale filelock lock file under the workspace if no other kernel instance is running","On Windows, exclude the workspace from antivirus real-time scanning","Check kernel logs — a failure here after a successful write of runtime state means the turn commit needs to be redone with the same commitTurnID"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight: confirm space and file writability before long agent turns\n// df -h <workspace> && test -w <workspace>/data/storage/ai/agent/sessions/<id>/session.json","typeGuard":"null","tryCatchPattern":"catch (e) {\n  if (/save session file failed/.test(e?.data?.msg ?? '')) {\n    // fix disk/lock cause; re-GET the session for the latest revision; retry the save once\n  }\n}","preventionTips":["Monitor free space during long agent sessions","Release stale filelock locks only when no other kernel instance is running","Retry saves from a freshly loaded state — revision may have advanced past your copy"],"tags":["agent","session","filesystem","disk-full"],"backgroundTag":"disk-write-failed","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}