id: hard-delete-session-jsonl title: Hard-delete session JSONL on conversation delete scenario: gateway-backend-communication taskType: runtime-bridge intent: Remove the on-disk session transcript (and its sibling artefacts) when the user deletes a conversation, instead of soft-deleting it via rename. touchedAreas:
- electron/main/ipc-handlers.ts
- electron/api/routes/sessions.ts
- electron/utils/session-files.ts
- src/stores/chat/session-actions.ts
- src/stores/chat.ts
- tests/unit/session-delete-route.test.ts
- harness/specs/tasks/hard-delete-session-jsonl.md
- AGENTS.md expectedUserBehavior:
- Confirming "Delete" in the sidebar conversation menu removes .jsonl, .deleted.jsonl and any .jsonl.reset.* siblings from the agent's sessions folder.
- OpenClaw's trajectory artefacts for the same session id are removed too — both the local .trajectory.jsonl flight recorder and the .trajectory-path.json pointer sidecar.
- When the pointer's runtimeFile points outside the sessions/ folder (the OPENCLAW_TRAJECTORY_DIR override), that off-disk runtime file is also unlinked so no orphan trajectory remains anywhere on disk.
- The session entry is removed from sessions.json so OpenClaw sessions.list stops returning it.
- The sidebar list, sessionLabels and sessionLastActivity for the deleted key are cleared in the renderer store.
- Any pending optimistic user message cache for the deleted session key is cleared so a later history reload cannot resurrect deleted chat bubbles.
- Token usage history reported by the Dashboard stops including the deleted session. requiredProfiles:
- fast
- comms requiredTests:
- tests/unit/session-delete-route.test.ts
- tests/unit/chat-session-actions.test.ts acceptance:
- Renderer continues to use src/lib/host-api.ts and src/lib/api-client.ts; no new direct ipcRenderer or Gateway HTTP calls.
- IPC channel name session:delete and HTTP route POST /api/sessions/delete are unchanged in shape.
- Both the IPC handler in electron/main/ipc-handlers.ts and the HTTP mirror in electron/api/routes/sessions.ts unlink the same set of files for a given session id, sharing electron/utils/session-files.ts so the disk contract cannot drift.
- The handler tolerates ENOENT (file already gone) and still updates sessions.json so the sidebar stops listing the entry.
- Renderer delete-session paths clear any in-memory pending optimistic user messages for the deleted key before subsequent history loads run.
- agentId from the sessionKey is validated against /[1][A-Za-z0-9_-]*$/ in both surfaces and any sessionFile resolved to a path outside the agent sessions/ directory is refused (defence-in-depth against a corrupt sessions.json).
- Absolute-path detection accepts POSIX paths, Windows backslash paths (C:...) and Windows forward-slash paths (C:/...) so the sweep works on every supported OS.
- The sweep also unlinks .trajectory.jsonl and .trajectory-path.json sidecars produced by OpenClaw's runtime trajectory writer.
- When .trajectory-path.json carries the openclaw-trajectory-pointer schema and an absolute .jsonl runtimeFile that lives outside the sessions/ folder, the off-disk runtime trajectory is unlinked too (covers the OPENCLAW_TRAJECTORY_DIR override). Pointers with the wrong schema, a non-.jsonl runtimeFile, or a non-absolute runtimeFile are ignored and no extra files are touched. docs: required: true
Conversation deletion in ClawX runs entirely on the Main process — the
OpenClaw Gateway does not expose a sessions.delete RPC. Historically the
operation was a soft delete: the live <id>.jsonl transcript was renamed to
<id>.deleted.jsonl so sessions.list would skip it. This task replaces
that rename with a true unlink plus a sibling sweep that also removes
<id>.deleted.jsonl (legacy soft-delete leftovers) and <id>.jsonl.reset.*
(reset snapshots produced by sessions.reset).
Both backends (the IPC handler used by the refactored chat store and the
HTTP route used by the legacy chat store) share the same disk contract via
electron/utils/session-files.ts, which centralises:
- sessions.json entry resolution across the three observed shapes,
- cross-platform absolute-path detection (POSIX, Windows
C:\...and WindowsC:/...) usingpath.isAbsolute+path.win32.isAbsolute, - a defence-in-depth scope check that refuses any
sessionFilewhose resolved directory escapes the agent'ssessions/folder, - the sibling sweep (
.jsonl,.deleted.jsonl,.jsonl.reset.*,.trajectory.jsonl,.trajectory-path.json) with ENOENT tolerance, and - the trajectory pointer-follow that handles OpenClaw's
OPENCLAW_TRAJECTORY_DIRoverride: when the pointer is well-formed (traceSchema === "openclaw-trajectory-pointer"andruntimeFileis an absolute.jsonloutside sessions/), the off-disk runtime file is unlinked too. Malformed or unsafe pointers are ignored.
Renderer surface is untouched: the confirm dialog in Sidebar.tsx, the
useChatStore.deleteSession API, and the host-api/api-client boundary all
continue to work without changes. The only user-visible effect is that
deleted conversations no longer leave hidden .deleted.jsonl files behind
and no longer contribute to Dashboard token-usage history. Deletion also
clears renderer-only pending optimistic user messages keyed by the deleted
session so the UI cannot rehydrate a locally staged message after the
transcript and sidebar entry have been removed.
A-Za-z0-9 ↩︎