Skip to content

[Bug]: isolated cron run session rows can inherit stale lifecycle fields (status/startedAt/endedAt) from prior runs #70619

Description

@f1yankees

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    dedupe:parentPrimary canonical item in dedupe cluster

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions