feat: disable automatic session resets by default#111140
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 267c6e95df
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| /** Deletes one session entry by agent/session identity. */ | ||
| export async function deleteSessionEntry(params: DeleteSessionEntryParams): Promise<boolean> { | ||
| const agentId = params.agentId ?? resolveAgentIdFromSessionKey(params.sessionKey); |
There was a problem hiding this comment.
Preserve the caller's explicit session-store scope
When storePath was resolved for one scope but contains keys for another agent—as closeDiscordThreadSessions explicitly expects with its main/work multi-key test—deriving agentId from each key changes the SQLite target after listSessionEntries({ storePath }) read it. A canonical path owned by the original account throws an agent mismatch, while a custom path resolves to another agent-suffixed database, so the listed entry is not deleted. Keep the supplied store's resolved ownership instead of overriding it from sessionKey (extensions/discord/src/monitor/thread-session-close.test.ts:99-121).
Useful? React with 👍 / 👎.
| const deleted = await deleteSessionEntry({ | ||
| archiveTranscript: true, | ||
| expectedSessionId: entry.sessionId ?? null, | ||
| expectedUpdatedAt: entry.updatedAt, |
There was a problem hiding this comment.
Avoid archiving sessions when the deletion guard fails
If a matching thread session changes between listSessionEntries and this guarded delete, the real SQLite lifecycle writes the .deleted.* transcript archive before checking expectedSessionId/expectedUpdatedAt, then returns without deleting when the guard fails (src/config/sessions/session-accessor.sqlite-lifecycle.ts:208-230). This leaves a false archive of a still-active session on every such race; the new concurrent-update test uses a mock and therefore misses the filesystem side effect. Validate the snapshot before requesting archive materialization, or clean up the materialized archive on rejection.
Useful? React with 👍 / 👎.
|
Codex review: needs real behavior proof before merge. Reviewed July 19, 2026, 12:45 AM ET / 04:45 UTC. Summary PR surface: Source +17, Tests +237, Docs +4. Total +258 across 27 files. Reproducibility: yes. from source: resolve an explicit store path containing an entry whose session key resolves to another agent, then call guarded deletion; the derived agent scope can diverge from the already-read store. The archive-on-rejected-guard race is also source-reproducible through a concurrent entry replacement between listing and delete. Review metrics: 1 noteworthy metric.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge
Maintainer decision needed
Security Review findings
Review detailsBest possible solution: Keep the new default only after preserving caller-supplied store ownership, making guarded thread-close deletion side-effect-free on rejection, and adding redacted real-runtime proof for default continuity, explicit reset modes, and Discord thread close. Do we have a high-confidence way to reproduce the issue? Yes, from source: resolve an explicit store path containing an entry whose session key resolves to another agent, then call guarded deletion; the derived agent scope can diverge from the already-read store. The archive-on-rejected-guard race is also source-reproducible through a concurrent entry replacement between listing and delete. Is this the best way to solve the issue? No. The session-default direction may be viable, but the current deletion helper must honor caller-resolved storage scope and make optimistic guard rejection free of archival side effects before it is a safe implementation. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against d5cb708623fa. Label changesLabel justifications:
Evidence reviewedPR surface: Source +17, Tests +237, Docs +4. Total +258 across 27 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
267c6e9 to
4cd755c
Compare
|
Merged via squash.
|
* feat(config): disable automatic session resets by default * fix(sessions): honor pending reset tombstones * test(sessions): align reset coverage with disabled default * fix(sessions): preserve explicit reset override fallback * fix(sessions): inherit active mode in partial type resets
What Problem This Solves
OpenClaw currently rolls unconfigured sessions to a new session every day at 04:00 local time. That automatic boundary discards rolling session continuity even though session state persists in SQLite, compaction already bounds the active model context, and memory layers carry durable continuity.
This is an owner-approved, compatibility-sensitive default change: compaction becomes the natural context boundary, while automatic session rollover becomes opt-in.
Why This Change Was Made
The default reset mode is now
none. Explicit daily and idle policies,resetByType,resetByChannel, legacyidleMinutes, and manual/newand/resetremain available and retain their existing behavior. Explicit reset objects that omitmodepreserve their previous daily fallback.The consumer sweep also found Discord thread close depended on the old timestamp-based freshness behavior. It now uses a guarded lifecycle deletion so closing a thread still archives its transcript and guarantees a fresh session on the next message under every reset mode. Pending
updatedAt: 0reset tombstones written by older releases remain honored after upgrade.User Impact
Users without a configured reset policy keep one rolling session across idle gaps and calendar boundaries, with compaction managing context growth. Operators who want automatic rollover can opt into
session.reset.mode: "daily"or"idle"; existing explicit schedules and compatibility settings continue to work.Cron isolated-agent runs still receive a fresh session identity per run. That behavior is driven by run-scoped cron session creation, not the session reset default. Heartbeat and other system-event writes still do not extend explicit daily or idle freshness.
Release-note context: call out that unconfigured sessions no longer reset daily. Operators who want the former behavior should configure
session.reset: { mode: "daily", atHour: 4 }. No changelog file is included in this contributor PR.AI-assisted: yes. I understand and reviewed the implementation and its compatibility boundaries.
Evidence
node scripts/run-vitest.mjs src/config/sessions/reset-policy.test.ts src/config/sessions/sessions.test.ts src/config/sessions/store.session-lifecycle-mutation.test.ts src/auto-reply/reply/session.test.ts src/auto-reply/reply/session.heartbeat-no-reset.test.ts src/cron/isolated-agent.session-identity.test.ts extensions/discord/src/monitor/thread-session-close.test.ts src/plugin-sdk/session-store-runtime.test.ts— 261 tests passed on the final rebased head in Blacksmith Testbox.pnpm tsgo:core— passed.pnpm tsgo:extensions— passed.pnpm plugin-sdk:surface:check— passed.pnpm format:checkfor all touched files — passed.git diff --check— passed.origin/main— clean, no accepted/actionable findings.