Skip to content

fix(cron): reuse existing sessionId for webhook/cron sessions#18031

Merged
steipete merged 2 commits intoopenclaw:mainfrom
Operative-001:fix/cron-session-reuse-18027
Feb 16, 2026
Merged

fix(cron): reuse existing sessionId for webhook/cron sessions#18031
steipete merged 2 commits intoopenclaw:mainfrom
Operative-001:fix/cron-session-reuse-18027

Conversation

@Operative-001
Copy link
Contributor

@Operative-001 Operative-001 commented Feb 16, 2026

Summary

  • Problem: Webhook-triggered agent sessions always start fresh, ignoring stable sessionKey even when allowRequestSessionKey is configured. Conversation history is never preserved.
  • Why it matters: Breaks any webhook integration that needs stateful conversations (chatbots, support agents, persistent API endpoints)
  • What changed: resolveCronSession now checks for existing session entries, evaluates freshness, and reuses sessionId when appropriate

Change Type

  • Bug fix

Scope

  • Gateway / orchestration (cron/webhook session handling)

Linked Issue/PR

User-visible / Behavior Changes

Webhook and cron job sessions now properly maintain conversation history when:

  1. hooks.allowRequestSessionKey: true is configured
  2. The same sessionKey is provided across invocations
  3. The session has not expired per configured reset policy

Previous behavior: Every invocation started fresh (isNewSession: true)
New behavior: Reuses existing session when fresh (isNewSession: false)

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • Any OS with OpenClaw 2026.2.x

Steps

  1. Configure hooks.allowRequestSessionKey: true
  2. POST to /hooks/agent twice with same sessionKey
  3. Previous: Second request has no conversation context
  4. After: Second request continues conversation with history

Expected

Webhook sessions preserve conversation history across calls

Actual

Implemented as expected. Session freshness evaluated per reset policy.

Evidence

  • 4 new regression tests added to session.test.ts
  • Tests cover: fresh reuse, stale expiration, forceNew override, missing sessionId handling

Human Verification

Verified scenarios:

  • Fresh session reuses existing sessionId ✓
  • Stale session creates new sessionId ✓
  • forceNew=true always creates new ✓
  • Missing sessionId in entry creates new ✓
  • Model/provider overrides preserved ✓

Root cause analysis:

  • resolveCronSession hardcoded const sessionId = crypto.randomUUID() unconditionally
  • Always returned isNewSession: true
  • Now checks entry existence and freshness first

Compatibility / Migration

  • Backward compatible? Yes - new sessions still work identically
  • Config/env changes? No - uses existing allowRequestSessionKey config
  • Migration needed? No

Risks and Mitigations

  • Risk: Sessions might persist longer than expected if reset policy is lenient
  • Mitigation: Uses same evaluateSessionFreshness as other channels, respects configured idleMinutes/daily reset

Greptile Summary

Fixed webhook/cron sessions to properly reuse existing sessionId when allowRequestSessionKey is configured and the session is still fresh, enabling stateful conversations across webhook invocations.

Key changes:

  • Added session freshness evaluation using evaluateSessionFreshness and resolveSessionResetPolicy
  • resolveCronSession now checks for existing sessions and reuses sessionId when appropriate (not stale, not forced new)
  • Preserved all session context fields when reusing sessions via spread operator
  • Added comprehensive test coverage (4 new test cases) for fresh reuse, stale expiration, forceNew override, and missing sessionId handling
  • Properly returns isNewSession: false when reusing, allowing conversation history to persist

The implementation correctly addresses the root cause where sessionId was previously hardcoded to always generate a new UUID. The fix uses the same session reset policy evaluation ("direct" type) that other channels use, ensuring consistent behavior across the platform.

Confidence Score: 4/5

  • Safe to merge with minimal risk - well-tested bug fix with proper backwards compatibility
  • The implementation correctly fixes the webhook session persistence issue and includes comprehensive test coverage. The logic properly evaluates session freshness and reuses sessions when appropriate. The spread operator usage is slightly redundant but not incorrect. Backwards compatible - new sessions work identically to before.
  • No files require special attention - both implementation and tests are solid

Last reviewed commit: 7d54a1a

When a webhook or cron job provides a stable sessionKey, the session
should maintain conversation history across invocations. Previously,
resolveCronSession always generated a new sessionId and hardcoded
isNewSession: true, preventing any conversation continuity.

Changes:
- Check if existing entry has a valid sessionId
- Evaluate freshness using configured reset policy
- Reuse sessionId and set isNewSession: false when fresh
- Add forceNew parameter to override reuse behavior
- Spread existing entry to preserve conversation context

This enables persistent, stateful conversations for webhook-driven
agent endpoints when allowRequestSessionKey is configured.

Fixes openclaw#18027
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 61 to 81
@@ -34,5 +79,5 @@ export function resolveCronSession(params: {
displayName: entry?.displayName,
skillsSnapshot: entry?.skillsSnapshot,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

redundant field assignments overwrite spread values

When isNewSession is false, line 63 spreads all fields from entry, but lines 68-80 then re-assign many of those same fields using entry?.field. This is redundant and could cause confusion - the spread already copied all fields.

Suggested change
const sessionEntry: SessionEntry = {
// Spread existing entry to preserve conversation context when reusing
...(isNewSession ? undefined : entry),
// Always update these core fields
sessionId,
updatedAt: params.nowMs,
systemSent,
};

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/isolated-agent/session.ts
Line: 61:81

Comment:
redundant field assignments overwrite spread values

When `isNewSession` is `false`, line 63 spreads all fields from `entry`, but lines 68-80 then re-assign many of those same fields using `entry?.field`. This is redundant and could cause confusion - the spread already copied all fields.

```suggestion
  const sessionEntry: SessionEntry = {
    // Spread existing entry to preserve conversation context when reusing
    ...(isNewSession ? undefined : entry),
    // Always update these core fields
    sessionId,
    updatedAt: params.nowMs,
    systemSent,
  };
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! You're right — the spread already copies those fields when reusing an existing session. Pushed a fix that removes the redundant assignments. Thanks for the review!

Addresses Greptile review comment: when !isNewSession, the spread already
copies all entry fields. The explicit entry?.field assignments were
redundant and could cause confusion. Simplified to only override the
core fields (sessionId, updatedAt, systemSent).
@youchi1
Copy link

youchi1 commented Feb 20, 2026

forceNew parameter is completely dead - not configurable anywhere. Now, every cron session is forced to use a shared session, which burns tokens...changing the default behavior and even without any configurability is not very smart. check #20092

@Operative-001

MunemHashmi added a commit to MunemHashmi/openclaw that referenced this pull request Feb 22, 2026
)

Adds a `sessionFreshness` field to `CronJob` with two values:
- `"reuse-if-fresh"` (default, existing behavior)
- `"always-new"` (forces a new session UUID on every run)

When set to `"always-new"`, each isolated cron run gets a fresh session
with no accumulated context from prior runs, fixing the contradiction
between docs and PR openclaw#18031 behavior.
MunemHashmi added a commit to MunemHashmi/openclaw that referenced this pull request Feb 22, 2026
)

Adds a `sessionFreshness` field to `CronJob` with two values:
- `"reuse-if-fresh"` (default, existing behavior)
- `"always-new"` (forces a new session UUID on every run)

When set to `"always-new"`, each isolated cron run gets a fresh session
with no accumulated context from prior runs, fixing the contradiction
between docs and PR openclaw#18031 behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Impossible to maintain conversation history for webhook agent runs despite stable sessionKey and configuration

3 participants