openclaw - ✅(Solved) Fix [Bug]: isolated cron run session rows can inherit stale lifecycle fields (status/startedAt/endedAt) from prior runs [1 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70619Fetched 2026-04-24 05:55:35
View on GitHub
Comments
2
Participants
2
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
commented ×2cross-referenced ×1

For sessionTarget: "isolated" cron jobs, a fresh per-run session row can inherit stale lifecycle metadata from older session state.

Observed stale fields include:

  • status
  • startedAt
  • endedAt

This produces run rows that look finalized before the new run has actually finished, or rows that end up with a mixed/partial state even though the underlying run really executed.

Error Message

A new current run existed under the base cron session key:

Root Cause

Suspected root cause

Fix Action

Fix / Workaround

  • a real sessionId is created
  • the transcript file exists
  • usage / summary patches may be written later

This suggests later merge/update paths can patch usage/summary data onto a bad initial row shape without reconstructing correct lifecycle metadata.

PR fix notes

PR #70656: fix(cron): clear stale lifecycle fields on isolated run session seed

Description (problem / solution / changelog)

Summary

Fixes #70619 — Isolated cron run session rows inherit stale status/startedAt/endedAt from prior runs.

Two root causes fixed:

  1. session.ts — When isNewSession is true, the spread block cleared delivery routing fields but NOT lifecycle fields. Added explicit status: undefined, startedAt: undefined, endedAt: undefined to the reset.

  2. run-session-state.ts — Per-run key (cron:<jobId>:run:<sessionId>) was assigned the same object reference as the base agent key. Post-run mutations to the base key silently polluted the shared object. Changed to shallow copy ({ ...sessionEntry }) so keys are independent.

Files changed

  • src/cron/isolated-agent/session.ts (5 lines) — clear lifecycle fields on new session
  • src/cron/isolated-agent/run-session-state.ts (8 lines) — break shared mutable reference
  • src/cron/isolated-agent/session.test.ts (44 lines) — two regression tests

Test plan

  • Run isolated cron job twice, verify second run has fresh startedAt and status=running
  • Verify per-run rows don't carry prior run's endedAt
  • Run new regression tests: npm test -- --grep "stale lifecycle"

🤖 Generated with Claude Code

Changed files

  • src/cron/isolated-agent/run-session-state.ts (modified, +6/-2)
  • src/cron/isolated-agent/session.test.ts (modified, +44/-0)
  • src/cron/isolated-agent/session.ts (modified, +5/-0)

Code Example

sessionEntry: {
  ...entry,
  sessionId,
  updatedAt,
  systemSent,
  ...
}
RAW_BUFFERClick to expand / collapse

Summary

For sessionTarget: "isolated" cron jobs, a fresh per-run session row can inherit stale lifecycle metadata from older session state.

Observed stale fields include:

  • status
  • startedAt
  • endedAt

This produces run rows that look finalized before the new run has actually finished, or rows that end up with a mixed/partial state even though the underlying run really executed.

Why this looks like an OpenClaw bug

The actual isolated run can still work:

  • a real sessionId is created
  • the transcript file exists
  • usage / summary patches may be written later

But the run-scoped session-store row can start life with stale lifecycle fields or an incomplete lifecycle shape.

So the execution path appears to work, while the persistence/bookkeeping path is wrong.

Observed behavior

A new current run existed under the base cron session key:

  • base key: agent:main:cron:<job-id>
  • sessionId = 4b218b3d-...
  • status = running

But the matching per-run row for that same sessionId was already wrong:

  • run key: agent:main:cron:<job-id>:run:4b218b3d-...
  • sessionId = 4b218b3d-...
  • status = done
  • startedAt = <timestamp from previous run>
  • endedAt = <timestamp from previous run>

That means the new run row was born with stale lifecycle fields from the previous run.

I also observed earlier run rows that had:

  • real transcript
  • real token stats
  • missing or inconsistent lifecycle fields

This suggests later merge/update paths can patch usage/summary data onto a bad initial row shape without reconstructing correct lifecycle metadata.

Expected behavior

For a fresh isolated cron run:

  • the per-run :run:<sessionId> row should start with fresh lifecycle state
  • it should not inherit prior-run status, startedAt, or endedAt
  • the base cron key and run-scoped key should not share a mutable session-entry object that can carry stale run-scoped fields

Actual behavior

Fresh isolated cron run rows appear to be seeded from prior/base session entry state and can inherit stale lifecycle fields.

This causes symptoms like:

  • run row marked done while the current run is actually active
  • mismatched startedAt / endedAt
  • partial rows with transcript + usage but missing correct lifecycle finalization
  • confusing timeout / zombie / bookkeeping artifacts even when the run did real work

Suspected root cause

1) resolveCronSession() copies prior entry state into the new session entry

The isolated-session creation path appears to seed the new session entry via something equivalent to:

sessionEntry: {
  ...entry,
  sessionId,
  updatedAt,
  systemSent,
  ...
}

Some routing fields are cleared for fresh sessions, but run-scoped lifecycle fields are not fully cleared.

2) createPersistCronSessionEntry() persists that session entry to both keys

The same session-entry object appears to be written to both:

  • the base cron key
  • the per-run :run:<sessionId> key

So if stale lifecycle fields are already present on that object, the fresh run row inherits them immediately.

3) later updates merge onto the existing row instead of rebuilding lifecycle state

Later usage/summary updates can make the row look partly real, but they do not necessarily repair incorrect or missing lifecycle fields.

Related issues

This seems closely related to the broader isolated-cron state-inheritance family:

  • #66380 isolated cron run sessions inherit stale heartbeat cache fields
  • #62707 resolveCronSession leaks stale CLI session identity on forceNew
  • #11506 cron sessions inherit stale model from prior session-store entry
  • #70421 session status not synced with endedAt
  • #70347 cron timeout path does not finalize lifecycle cleanly

This report is specifically about stale inherited lifecycle fields on fresh isolated cron run rows.

Minimal fix direction

  1. In the fresh isolated-run path, explicitly clear run-scoped lifecycle fields such as:

    • status
    • startedAt
    • endedAt
  2. Avoid persisting the exact same mutable session-entry object to both:

    • base cron key
    • run key
  3. Prefer treating per-run :run:<sessionId> entries as immutable run snapshots rather than mutable shared state.

Impact

This makes cron runs hard to trust operationally because a run may:

  • really execute
  • produce a transcript
  • produce token stats

while still appearing stale, pre-finalized, or half-corrupted in session bookkeeping.

extent analysis

TL;DR

Clearing run-scoped lifecycle fields in the fresh isolated-run path and avoiding shared mutable session-entry objects can help resolve the issue of stale inherited lifecycle fields on fresh isolated cron run rows.

Guidance

  • Explicitly clear run-scoped lifecycle fields such as status, startedAt, and endedAt in the fresh isolated-run path to prevent inheritance of stale values.
  • Ensure that the session-entry object is not shared between the base cron key and the per-run key to prevent mutable state issues.
  • Consider treating per-run entries as immutable run snapshots to improve data consistency and trustworthiness.
  • Review related issues (#66380, #62707, #11506, #70421, #70347) to address broader isolated-cron state-inheritance problems.

Example

// Example of clearing run-scoped lifecycle fields
const freshSessionEntry = {
  ...entry,
  sessionId,
  updatedAt,
  systemSent,
  status: 'running', // Reset status
  startedAt: new Date(), // Reset startedAt
  endedAt: null, // Reset endedAt
};

Notes

The provided fix direction is a minimal approach to address the specific issue of stale inherited lifecycle fields. However, a more comprehensive solution may require addressing the broader isolated-cron state-inheritance family of issues.

Recommendation

Apply the workaround of clearing run-scoped lifecycle fields and avoiding shared mutable session-entry objects to improve the trustworthiness of cron runs. This approach can help mitigate the issue until a more comprehensive solution is implemented.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

For a fresh isolated cron run:

  • the per-run :run:<sessionId> row should start with fresh lifecycle state
  • it should not inherit prior-run status, startedAt, or endedAt
  • the base cron key and run-scoped key should not share a mutable session-entry object that can carry stale run-scoped fields

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: isolated cron run session rows can inherit stale lifecycle fields (status/startedAt/endedAt) from prior runs [1 pull requests, 2 comments, 2 participants]