Skip to content

Fix Claude CLI session lifecycle continuity#70106

Merged
obviyus merged 4 commits into
openclaw:mainfrom
obviyus:fix/claude-cli-session-lifecycle
Apr 22, 2026
Merged

Fix Claude CLI session lifecycle continuity#70106
obviyus merged 4 commits into
openclaw:mainfrom
obviyus:fix/claude-cli-session-lifecycle

Conversation

@obviyus

@obviyus obviyus commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Keep provider-owned CLI sessions through implicit daily expiry while preserving explicit /reset and configured reset policies.
  • Let legacy CLI bindings without mcpResumeHash upgrade via existing mcpConfigHash checks.
  • Preserve rich cliSessionBindings when gateway agent requests refresh session state.

Tests

  • pnpm test src/agents/cli-session.test.ts src/auto-reply/reply/session.test.ts src/gateway/server.agent.gateway-server-agent-b.test.ts
  • pnpm check:changed
  • pnpm exec oxfmt --check docs/concepts/session.md docs/gateway/cli-backends.md src/agents/cli-session.test.ts src/agents/cli-session.ts src/auto-reply/reply/session.test.ts src/auto-reply/reply/session.ts src/config/sessions/reset-policy.ts src/gateway/server-methods/agent.ts src/gateway/server.agent.gateway-server-agent-b.test.ts

@aisle-research-bot

aisle-research-bot Bot commented Apr 22, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Implicit daily session expiry bypass for provider-owned CLI sessions can cause indefinite sensitive state retention
2 🟡 Medium Gateway session APIs may expose provider-owned CLI session binding metadata (cliSessionBindings) to clients
1. 🟡 Implicit daily session expiry bypass for provider-owned CLI sessions can cause indefinite sensitive state retention
Property Value
Severity Medium
CWE CWE-922
Location src/auto-reply/reply/session.ts:456-460

Description

In initSessionState, sessions that have a provider-owned CLI binding are forced to be treated as fresh whenever the session reset policy was not explicitly configured.

  • The default/implicit daily reset is bypassed via skipImplicitExpiry.
  • Because the session is treated as fresh, rollover logic (new session id, transcript archiving, state clearing) does not run.
  • This can unintentionally create long-lived gateway sessions retaining chat transcripts and other sensitive context on disk, increasing exposure on stolen session keys, shared devices, or account takeover.

Vulnerable logic:

const skipImplicitExpiry = hasProviderOwnedSession(entry) && resetPolicy.configured !== true;
const entryFreshness = entry
  ? isSystemEvent || skipImplicitExpiry
    ? ({ fresh: true } satisfies SessionFreshness)
    : evaluateSessionFreshness({ updatedAt: entry.updatedAt, now, policy: resetPolicy })
  : undefined;

resetPolicy.configured is set based on whether any reset setting is present; when not configured, the default daily reset exists operationally but is not enforced for these sessions.

Recommendation

Do not disable the implicit daily expiry for provider-owned CLI sessions by default. Options:

  1. Preserve CLI continuity while still rolling the gateway session (recommended): on daily reset boundaries, start a new gateway session and archive transcripts, but carry forward only the CLI binding.

  2. Add a hard maximum age for provider-owned sessions (e.g., 24h or configurable) even when session.reset is not explicitly configured.

Example approach (preserve binding while resetting rest):

const cliBinding = provider ? getCliSessionBinding(entry, provider) : undefined;
const entryFreshness = entry
  ? isSystemEvent
    ? ({ fresh: true } satisfies SessionFreshness)
    : evaluateSessionFreshness({ updatedAt: entry.updatedAt, now, policy: resetPolicy })
  : undefined;// if stale daily but provider-owned, roll session but reattach cli binding to new entry
if (entry && cliBinding && entryFreshness?.dailyResetAt && entry.updatedAt < entryFreshness.dailyResetAt) {// proceed with rollover, but copy cliBinding into the new session entry
}

Also document and/or make this behavior an explicit config flag (opt-in) to avoid surprising data retention changes.

2. 🟡 Gateway session APIs may expose provider-owned CLI session binding metadata (cliSessionBindings) to clients
Property Value
Severity Medium
CWE CWE-200
Location src/gateway/server-methods/agent.ts:664-692

Description

The gateway now persists cliSessionBindings in the session entry patch during agent requests. This metadata includes provider session identifiers and account/profile linkage fields (e.g., authProfileId, mcpConfigHash, mcpResumeHash).

Because gateway session endpoints (e.g., sessions.reset) return the session entry object to connected clients, persisting these bindings increases the risk of information disclosure:

  • cliSessionBindings is copied into the updated session entry on each agent refresh, ensuring it remains present in the session store.
  • Gateway session RPC responses can include entry.cliSessionBindings (see sessions.reset test expectations), meaning this provider-owned metadata can be sent over the gateway API/WebSocket to any client that is authorized to call these endpoints.

Vulnerable change (persistence/enabling propagation):

cliSessionIds: entry?.cliSessionIds,
cliSessionBindings: entry?.cliSessionBindings,
claudeCliSessionId: entry?.claudeCliSessionId,

If the gateway is used in a multi-client or multi-tenant context (or if less-trusted clients can connect), this can leak identifiers that link a user/session to a specific provider account/profile and may aid session correlation or other abuse.

Recommendation

Treat cliSessionBindings as server-only metadata.

  • Do not include cliSessionBindings in session entries that are returned to gateway clients.
  • If persistence is needed for backend reuse, keep it in a separate server-side store/namespace or explicitly strip it from any RPC response payloads.

Example: strip before returning entry from session APIs:

function sanitizeSessionEntryForClient(entry: SessionEntry): SessionEntry {
  const { cliSessionBindings, ...rest } = entry;
  return rest;
}

Additionally:

  • Ensure only trusted/internal clients can call endpoints that return full session entries.
  • Consider encrypting or hashing provider identifiers if they must traverse client boundaries.

Analyzed PR: #70106 at commit 42777c7

Last updated on: 2026-04-22T10:05:59Z

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime agents Agent runtime and tooling size: M maintainer Maintainer-authored PR labels Apr 22, 2026
@greptile-apps

greptile-apps Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes CLI session lifecycle continuity across three dimensions: preventing implicit daily resets from cutting provider-owned CLI sessions, allowing legacy bindings without mcpResumeHash to fall back to mcpConfigHash validation, and preserving rich cliSessionBindings metadata when the gateway agent refreshes session state. The configured flag on SessionResetPolicy cleanly distinguishes user-configured policies from the implicit default, and the hasProviderOwnedSession guard composes correctly with the existing isSystemEvent path.

Confidence Score: 4/5

Safe to merge after confirming the downgrade path for mcpResumeHash is intentional or adding a covering test.

One P2 gap in the mcpResumeHash && change: when a stored binding has mcpResumeHash but the current client omits it, the code silently bypasses resume-hash validation and falls through to mcpConfigHash. This direction is untested. If the downgrade scenario is expected to arise in practice, a test (and possibly stricter handling) would reduce risk. All other changes are well-reasoned and covered by new tests.

src/agents/cli-session.ts — the downgrade direction of the mcpResumeHash fallback (stored has hash, current client does not) deserves a test or an explicit comment justifying the behavior.

Comments Outside Diff (1)

  1. src/agents/cli-session.ts, line 155-165 (link)

    P2 Unhandled downgrade path in MCP hash fallback

    The && change correctly lets a legacy stored binding (no mcpResumeHash) be validated via mcpConfigHash when the current client provides one, but it also silently opens a reverse path: a stored binding that does have mcpResumeHash is no longer checked against it when the current client omits mcpResumeHash — the code falls straight through to mcpConfigHash. If the MCP server composition changed between sessions (different mcpResumeHash, same mcpConfigHash), that stale session would still be reused. The test suite only covers the intended upgrade direction (stored = no hash, current = hash), not the downgrade direction (stored = hash, current = no hash).

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agents/cli-session.ts
    Line: 155-165
    
    Comment:
    **Unhandled downgrade path in MCP hash fallback**
    
    The `&&` change correctly lets a legacy stored binding (no `mcpResumeHash`) be validated via `mcpConfigHash` when the current client provides one, but it also silently opens a reverse path: a stored binding that *does* have `mcpResumeHash` is no longer checked against it when the current client omits `mcpResumeHash` — the code falls straight through to `mcpConfigHash`. If the MCP server composition changed between sessions (different `mcpResumeHash`, same `mcpConfigHash`), that stale session would still be reused. The test suite only covers the intended upgrade direction (stored = no hash, current = hash), not the downgrade direction (stored = hash, current = no hash).
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/cli-session.ts
Line: 155-165

Comment:
**Unhandled downgrade path in MCP hash fallback**

The `&&` change correctly lets a legacy stored binding (no `mcpResumeHash`) be validated via `mcpConfigHash` when the current client provides one, but it also silently opens a reverse path: a stored binding that *does* have `mcpResumeHash` is no longer checked against it when the current client omits `mcpResumeHash` — the code falls straight through to `mcpConfigHash`. If the MCP server composition changed between sessions (different `mcpResumeHash`, same `mcpConfigHash`), that stale session would still be reused. The test suite only covers the intended upgrade direction (stored = no hash, current = hash), not the downgrade direction (stored = hash, current = no hash).

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

Reviews (1): Last reviewed commit: "fix(gateway): preserve cli session bindi..." | Re-trigger Greptile

@obviyus obviyus self-assigned this Apr 22, 2026
@obviyus
obviyus force-pushed the fix/claude-cli-session-lifecycle branch from 4dca716 to 42777c7 Compare April 22, 2026 10:03
@obviyus
obviyus merged commit 16f016f into openclaw:main Apr 22, 2026
88 checks passed
@obviyus

obviyus commented Apr 22, 2026

Copy link
Copy Markdown
Contributor Author

Landed on main.

@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: 42777c79d5

ℹ️ 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".

groupChannel: resolvedGroupChannel ?? entry?.groupChannel,
space: resolvedGroupSpace ?? entry?.space,
cliSessionIds: entry?.cliSessionIds,
cliSessionBindings: entry?.cliSessionBindings,

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 Badge Avoid overwriting newer CLI binding metadata

nextEntryPatch copies entry?.cliSessionBindings before updateSessionStore(...) re-reads the store, so overlapping requests for the same session can lose newer binding metadata: a concurrent writer can update cliSessionBindings in the locked store snapshot, then this stale patch value overwrites it during mergeSessionEntry. That drops newer auth/MCP hashes and can cause avoidable CLI session invalidations on subsequent turns. Read/preserve this field from the in-lock store[primaryKey] instead of the pre-lock entry snapshot.

Useful? React with 👍 / 👎.

medikoo pushed a commit to medikoo/openclaw that referenced this pull request Apr 24, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
zhonghe0615 pushed a commit to zhonghe0615/openclaw that referenced this pull request May 7, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant