Skip to content

fix(agents): use atomic store helper for CLI session clearing#70298

Merged
steipete merged 3 commits into
openclaw:mainfrom
HFConsultant:contribution/session-recovery-improvements
Apr 22, 2026
Merged

fix(agents): use atomic store helper for CLI session clearing#70298
steipete merged 3 commits into
openclaw:mainfrom
HFConsultant:contribution/session-recovery-improvements

Conversation

@HFConsultant

Copy link
Copy Markdown
Contributor

What

  • Bug fix: clearCliSession and clearAllCliSessions used delete to remove CLI session properties. When mergeSessionEntry later spreads the update object over the persisted store entry, deleted keys are absent from the spread — so stale session IDs survive the merge and the expired session is never actually cleared.

    Changed to = undefined so the key is explicitly present in the spread.

  • Refactor: Extracted clearCliSessionInStore() in session-store.ts — an atomic helper that clears + persists + merges in one call, matching the existing updateSessionStoreAfterAgentRun pattern.

  • Dead code removal: Removed unreachable retry logic in cli-runner.ts that attempted session recovery without store access (duplicated by the caller in attempt-execution.ts).

Why

Without this fix, CLI sessions that expire mid-run are not properly cleared from the store. The agent keeps retrying with the stale session ID instead of creating a fresh session, causing repeated session_expired failures.

Testing

  • All existing agent tests pass (vitest --project agents — 382 files, 4000 tests)
  • Specifically: attempt-execution.cli.test.ts covers the session-expired recovery path end-to-end
  • oxlint + tsc --noEmit clean

AI Disclosure

  • AI-assisted (Antigravity / Claude Opus 4.6)
  • Fully tested locally
  • Author understands the changes
  • No Codex access for local review

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Apr 22, 2026
@greptile-apps

greptile-apps Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where delete on session fields was used before a store merge — since deleted keys are absent from object spreads, stale CLI session IDs survived mergeSessionEntry and expired sessions were never actually cleared. The fix (= undefined) is correct and the new clearCliSessionInStore helper cleanly encapsulates the clear-persist-merge pattern already established by updateSessionStoreAfterAgentRun.

Confidence Score: 5/5

Safe to merge — core fix is correct and well-scoped; only edge-case concern is a minor behavioral difference when the session entry is unexpectedly absent.

All findings are P2 or lower. The root-cause fix (delete → undefined) is correct, the new helper mirrors an established pattern, and the dead code removal is unambiguous. The one noted behavioral change (params.sessionEntry clobbered to undefined when key is missing) is an edge case unlikely to occur during a session_expired error.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/command/attempt-execution.ts
Line: 305-310

Comment:
**Behavioral change when session key is missing**

In the old code, the update was guarded by `if (entry) { ... params.sessionEntry = updatedEntry }` — if the key was absent from the store, `params.sessionEntry` was left unchanged. The new code unconditionally assigns the result of `clearCliSessionInStore`, which returns `undefined` when the key is missing, so `params.sessionEntry` is clobbered to `undefined` in that edge case.

While a `session_expired` error almost always implies the entry exists, this is a silent state change that could affect downstream consumers of `params.sessionEntry` if the entry ever goes missing mid-run.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(agents): use atomic store helper for..." | Re-trigger Greptile

Comment thread src/agents/command/attempt-execution.ts Outdated
Comment on lines +305 to +310
params.sessionEntry = await clearCliSessionInStore({
provider: params.providerOverride,
sessionKey: params.sessionKey,
sessionStore: params.sessionStore,
storePath: params.storePath,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Behavioral change when session key is missing

In the old code, the update was guarded by if (entry) { ... params.sessionEntry = updatedEntry } — if the key was absent from the store, params.sessionEntry was left unchanged. The new code unconditionally assigns the result of clearCliSessionInStore, which returns undefined when the key is missing, so params.sessionEntry is clobbered to undefined in that edge case.

While a session_expired error almost always implies the entry exists, this is a silent state change that could affect downstream consumers of params.sessionEntry if the entry ever goes missing mid-run.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/command/attempt-execution.ts
Line: 305-310

Comment:
**Behavioral change when session key is missing**

In the old code, the update was guarded by `if (entry) { ... params.sessionEntry = updatedEntry }` — if the key was absent from the store, `params.sessionEntry` was left unchanged. The new code unconditionally assigns the result of `clearCliSessionInStore`, which returns `undefined` when the key is missing, so `params.sessionEntry` is clobbered to `undefined` in that edge case.

While a `session_expired` error almost always implies the entry exists, this is a silent state change that could affect downstream consumers of `params.sessionEntry` if the entry ever goes missing mid-run.

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 44b033a9dd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/cli-runner.ts
Comment on lines 100 to 101
if (isFailoverError(err)) {
const retryableSessionId = context.reusableCliSession.sessionId ?? params.cliSessionId;
// Check if this is a session expired error and we have a session to clear
if (err.reason === "session_expired" && retryableSessionId && params.sessionKey) {
// Clear the expired session ID from the session entry
// This requires access to the session store, which we don't have here
// We'll need to modify the caller to handle this case

// For now, retry without the session ID to create a new session
const output = await executePreparedCliRun(context, undefined);
const effectiveCliSessionId = output.sessionId;
return buildCliRunResult({ output, effectiveCliSessionId });
}
throw err;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep session-expired recovery in runPreparedCliAgent

The new if (isFailoverError(err)) { throw err; } path removes the runner-level retry for session_expired, but other production callers still rely on runCliAgent to handle that case. I checked src/cron/isolated-agent/run-executor.ts:123-145 and src/auto-reply/reply/agent-runner-execution.ts:869-923: both pass persisted CLI session IDs and do not implement their own clear-and-retry flow on session_expired. With this change, expired sessions now bubble up as hard failures (especially when no alternate fallback candidate exists) instead of retrying once with a fresh CLI session, which is a behavior regression introduced by this commit.

Useful? React with 👍 / 👎.

@steipete

Copy link
Copy Markdown
Contributor

Maintainer triage: this needs fixup before merge.

Current CI has real unit failures in src/agents/cli-runner.reliability.test.ts around session-expired recovery retry behavior. Please either preserve the existing runCliAgent retry contract or update the contract/tests deliberately with coverage at the new caller boundary.

Also, this is adjacent to #70177 but does not fix it. #70177 needs transcript-backed resume validation for Claude sessions: if a stored Claude session id has no backing JSONL transcript/content, invalidate the binding with an explicit reason instead of resuming a ghost session.

HFConsultant and others added 3 commits April 22, 2026 19:55
- Bug fix: `clearCliSession` and `clearAllCliSessions` used `delete` to
  remove CLI session properties. When `mergeSessionEntry` later spreads
  the update object over the persisted store entry, deleted keys are
  absent from the spread, so stale session IDs survive the merge and the
  expired session is never actually cleared. Changed to `= undefined`.

- Refactor: Extracted `clearCliSessionInStore()` in `session-store.ts`
  — an atomic helper that clears + persists + merges in one call,
  matching the existing `updateSessionStoreAfterAgentRun` pattern.

- Dead code removal: Removed unreachable retry logic in `cli-runner.ts`
  that attempted session recovery without store access (duplicated by
  the caller in `attempt-execution.ts`).
Addresses CI failures and bot review feedback on PR openclaw#70298:

- Restore session-expired retry in cli-runner.ts — the runner-level retry
  is a safety net for callers (run-executor, agent-runner-execution) that
  do not implement their own clear-and-retry flow.

- Add nil-guard on clearCliSessionInStore result in attempt-execution.ts
  so params.sessionEntry is preserved when the store key is unexpectedly
  absent (Greptile P2).
@steipete
steipete force-pushed the contribution/session-recovery-improvements branch from 442d535 to 44846ee Compare April 22, 2026 19:01
@steipete
steipete merged commit 647f4ee into openclaw:main Apr 22, 2026
10 of 11 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm test src/agents/command/session-store.test.ts src/agents/command/attempt-execution.cli.test.ts src/agents/cli-runner.reliability.test.ts; pnpm check:changed; OPENCLAW_VITEST_MAX_WORKERS=1 pnpm check:changed --staged
  • Source head: 44846ee
  • Merge commit: 647f4ee

Thanks @HFConsultant!

@HFConsultant

Copy link
Copy Markdown
Contributor Author

Gerne! Danke, dass du es Open Source hältst 🙂

import { resolveMessageChannel } from "../../utils/message-channel.js";
import { resolveBootstrapWarningSignaturesSeen } from "../bootstrap-budget.js";
import { runCliAgent } from "../cli-runner.js";
import { clearCliSession, getCliSessionBinding, setCliSessionBinding } from "../cli-session.js";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medikoo pushed a commit to medikoo/openclaw that referenced this pull request Apr 24, 2026
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
zhonghe0615 pushed a commit to zhonghe0615/openclaw that referenced this pull request May 7, 2026
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants