Skip to content

fix(agents): heartbeat/cron prompts carry stale "Current time" that never refreshes between runs#45071

Closed
xinhuagu wants to merge 1 commit into
openclaw:mainfrom
xinhuagu:fix-44993-stale-heartbeat-timestamp
Closed

fix(agents): heartbeat/cron prompts carry stale "Current time" that never refreshes between runs#45071
xinhuagu wants to merge 1 commit into
openclaw:mainfrom
xinhuagu:fix-44993-stale-heartbeat-timestamp

Conversation

@xinhuagu

Copy link
Copy Markdown
Contributor

Problem

appendCronStyleCurrentTimeLine has an early-return guard that skips timestamp injection when the text already contains a "Current time:" line:

if (!base || base.includes("Current time:")) {
    return base; // stale timestamp preserved
}

This means heartbeat and cron prompts carry the first timestamp indefinitely — the model sees a frozen clock that never updates between runs.

Reported in #44993 with detailed repro: heartbeat polls and cron events show timestamps from hours or days ago.

Fix

Replace the early-return guard with a regex substitution that refreshes the existing Current time: line with the current nowMs value on every call. When no line exists, one is appended as before.

Before: base.includes("Current time:") → return base (stale)
After: base.replace(/^Current time:.*$/m, freshTimeLine) (refreshed)

This also fixes the same stale-timestamp path in session-reset-prompt.ts and post-compaction-context.ts, which reuse the same helper.

Tests

Added src/agents/current-time.test.ts with 6 test cases:

  • Appends timestamp when none exists
  • Returns empty/whitespace strings unchanged
  • Refreshes a stale timestamp with a fresh one (core regression test)
  • Preserves surrounding text when refreshing
  • No duplicate Current time: lines after repeated calls
  • resolveCronStyleNow returns expected format

Closes #44993

@greptile-apps

greptile-apps Bot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a longstanding stale-timestamp bug in appendCronStyleCurrentTimeLine where the early-return guard base.includes("Current time:") caused heartbeat and cron prompts to carry the timestamp from the very first run indefinitely. The fix swaps that guard for a regex substitution (/^Current time:.*$/m) that always replaces the existing line with a fresh one, while still appending when no line is present.

Key observations:

  • The core logic change is correct and minimal — one guard removed, one regex branch added.
  • The regex is constructed fresh on every call (no lastIndex state leakage), and .replace() without the g flag intentionally updates only the first match, which is the right behavior given the no-duplicate invariant enforced by the same function.
  • post-compaction-context.ts already worked around this bug by calling resolveCronStyleNow directly; it is not broken by this change. The PR description slightly overstates the scope of the fix for that file.
  • session-reset-prompt.ts calls the helper with a constant string that never contains Current time:, so it was not affected by the original bug, but it will benefit if callers ever pass a previously-enriched prompt string.
  • Tests are thorough and directly exercise the regression scenario.

Confidence Score: 5/5

  • This PR is safe to merge — the change is small, well-tested, and strictly improves correctness with no regressions.
  • The fix is minimal and targeted: one condition removed, one regex branch added. The regex is stateless (fresh object per call), replacement semantics are correct, and six new tests directly cover the regression scenario plus all relevant edge cases. Callers in heartbeat-runner.ts and the web variant pass freshly resolved startedAt/nowMs values, so the refreshed timestamp will always be current. No existing tests were modified and no unrelated files were touched.
  • No files require special attention.

Last reviewed commit: 2f0bf3b

@xinhuagu xinhuagu changed the title fix(agents): refresh stale "Current time" line in heartbeat/cron prompts fix(agents): heartbeat/cron prompts carry stale "Current time" that never refreshes between runs Mar 13, 2026
appendCronStyleCurrentTimeLine previously returned text unchanged when
it already contained a "Current time:" line, preserving stale timestamps
across heartbeat runs. Replace the early-return guard with a regex
substitution so each invocation refreshes the timestamp.

Closes openclaw#44993
@xinhuagu
xinhuagu force-pushed the fix-44993-stale-heartbeat-timestamp branch from 2f0bf3b to 80cc87c Compare March 13, 2026 13:15

@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: 80cc87c093

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

Comment on lines +40 to +42
const existingTimeRe = /^Current time:.*$/m;
if (existingTimeRe.test(base)) {
return base.replace(existingTimeRe, timeLine);

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 Restrict timestamp replacement to generated time lines

The new regex replaces any line that starts with Current time: anywhere in the prompt, not just the helper-appended timestamp line. In the heartbeat cron path, buildCronEventPrompt embeds raw event text (src/infra/heartbeat-events-filter.ts), so a user reminder containing a line like Current time: call ops at 5pm will be silently rewritten to the runtime timestamp, changing reminder semantics. This regression is introduced by base.replace(existingTimeRe, timeLine) and can corrupt user-authored cron content when that phrase appears at line start.

Useful? React with 👍 / 👎.

@steipete

Copy link
Copy Markdown
Contributor

Closing this as not reproducible on current main after Codex review.

Closing as cannot reproduce. On current main, the stale-between-runs path described in the PR does not exist: heartbeat prompts are rebuilt from raw config/system-event text for each run, and the previously injected timestamp is not fed back into appendCronStyleCurrentTimeLine. The proposed regex replacement would also rewrite arbitrary user-authored cron reminder lines that start with "Current time:".

What I checked:

  • Helper only skips when input already contains a time line: appendCronStyleCurrentTimeLine still returns early when the incoming text already contains "Current time:"; the PR targets this branch. (src/agents/current-time.ts:33, 40be5ad58187)
  • Heartbeat prompt is rebuilt from raw queued event text each run: resolveHeartbeatPromptInput maps queued entries to event.text and later injects the timestamp only when constructing ctx.Body, so the helper output is not reused as the next run's base prompt. (src/infra/heartbeat-runner.ts:669, 40be5ad58187)
  • System event queue stores raw text, not enriched heartbeat bodies: enqueueSystemEvent trims and stores the provided text as event.text. There is no timestamp-refresh loop through this queue. (src/infra/system-events.ts:90, 40be5ad58187)
  • Cron prompts embed user event text verbatim: buildCronEventPrompt concatenates pending event text directly into the reminder body. Replacing the first /^Current time:/ line would mutate user-authored reminder content, matching the regression called out in review. (src/infra/heartbeat-events-filter.ts:14, 40be5ad58187)
  • Post-compaction path already avoids the helper entirely: post-compaction-context.ts now calls resolveCronStyleNow() directly and explicitly documents why substring-gating on "Current time:" is unsafe there, so the PR body overstates the helper's affected scope. (src/auto-reply/reply/post-compaction-context.ts:135, 40be5ad58187)

Review notes: reviewed against 40be5ad58187.

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.

[Bug]: Heartbeat/Cron "Current time" timestamp is stale - not refreshing between runs

2 participants