Skip to content

fix(cron): refresh stale Current time timestamp on repeated heartbeat runs#45188

Closed
jackjin1997 wants to merge 2 commits into
openclaw:mainfrom
jackjin1997:fix/cron-stale-current-time
Closed

fix(cron): refresh stale Current time timestamp on repeated heartbeat runs#45188
jackjin1997 wants to merge 2 commits into
openclaw:mainfrom
jackjin1997:fix/cron-stale-current-time

Conversation

@jackjin1997

Copy link
Copy Markdown
Contributor

Problem

appendCronStyleCurrentTimeLine in src/agents/current-time.ts skips injecting a fresh timestamp if the prompt already contains Current time:. In practice, when a heartbeat/cron prompt is built from workspace content or carries over context from a previous run, the Current time: line goes stale and is never refreshed — the agent reasons about a time that may be hours old.

The same guard exists in src/auto-reply/reply/post-compaction-context.ts, where a comment already explains it is wrong: "Always append the real runtime timestamp — AGENTS.md content may itself contain 'Current time:' as user-authored text, so we must not gate on that substring."

Fix

Replace the early-return guard with an in-place replacement: if a Current time: line is found, overwrite it with the current timestamp; otherwise append as before.

-  if (!base || base.includes("Current time:")) {
-    return base;
-  }
-  const { timeLine } = resolveCronStyleNow(cfg, nowMs);
-  return `${base}\n${timeLine}`;
+  if (!base) {
+    return base;
+  }
+  const { timeLine } = resolveCronStyleNow(cfg, nowMs);
+  // Replace any existing stale "Current time:" line rather than skipping, so
+  // repeated heartbeat/cron invocations always carry a fresh timestamp.
+  const existingTimeRe = /^Current time:.*$/m;
+  if (existingTimeRe.test(base)) {
+    return base.replace(existingTimeRe, timeLine);
+  }
+  return `${base}\n${timeLine}`;

Tests

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

  • appends timestamp when none present
  • replaces stale timestamp on repeated runs (the regression case)
  • returns empty string unchanged
  • returns whitespace-only string as empty

All 4 tests pass. pnpm check clean.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS labels Mar 13, 2026
@greptile-apps

greptile-apps Bot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a stale-timestamp bug in appendCronStyleCurrentTimeLine (src/agents/current-time.ts): the old guard returned early whenever "Current time:" was already present in the prompt, so heartbeat/cron prompts built from prior-run context were never refreshed. The fix replaces the skipped-return with an in-place regex substitution that overwrites the existing line, falling back to appending when none is found — consistent with the comment already present in post-compaction-context.ts.

Changes:

  • src/agents/current-time.ts: removes the base.includes("Current time:") early-return; adds /^Current time:.*$/m replace-or-append logic with an explanatory comment.
  • src/agents/current-time.test.ts: new test file covering append (no prior timestamp), replace (repeated run / regression case), empty-string passthrough, and whitespace-only normalization.
  • CHANGELOG.md: minor blank-line formatting only.

One edge case to be aware of: the regex replaces the first Current time: line found in base. If user-authored cron task content happens to contain the literal text Current time: before the previously-appended timestamp, the user-authored line would be overwritten and the stale appended line would remain. This is the same class of concern documented in post-compaction-context.ts (which avoids it by always appending). In practice this is unlikely for heartbeat prompts, but it is a behavioural difference from the post-compaction-context strategy worth keeping in mind if the function is reused in contexts where AGENTS.md-derived content flows in as base.

Confidence Score: 5/5

  • This PR is safe to merge — it fixes a well-understood regression with a targeted change and accompanying tests.
  • The change is small and surgical (one function, ~6 lines changed), the regression case is directly tested, and the logic (replace-or-append via a non-global regex) is correct. No API surface changes, no new dependencies, and pnpm check is reported clean.
  • No files require special attention.

Last reviewed commit: ebaee01

@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: ebaee014d0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/current-time.ts Outdated
Comment on lines +41 to +43
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 Limit replacement to the injected runtime timestamp line

appendCronStyleCurrentTimeLine now replaces the first line that starts with Current time: regardless of where it came from, so any user-authored heartbeat prompt line with that prefix (for example a template or instruction in agents.defaults.heartbeat.prompt, which is passed through by resolveHeartbeatPrompt in src/infra/heartbeat-runner.ts and src/web/auto-reply/heartbeat-runner.ts) gets overwritten on every run. This silently mutates prompt content that is not the stale runtime line the change intended to refresh, and can remove important user instructions; the replacement should be scoped to the known injected timestamp pattern (or append unconditionally) instead of matching any Current time: line.

Useful? React with 👍 / 👎.

@jackjin1997
jackjin1997 force-pushed the fix/cron-stale-current-time branch from ebaee01 to 67dc6e4 Compare March 13, 2026 14:43

@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: 67dc6e4224

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/current-time.ts Outdated
Comment on lines +41 to +43
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 replacement to generated timestamp lines

The new ^Current time:.*$ match in appendCronStyleCurrentTimeLine replaces the first line with that prefix regardless of where it came from. In heartbeat flows (src/infra/heartbeat-runner.ts and src/web/auto-reply/heartbeat-runner.ts), user-authored prompt text is passed through before this helper runs, so a legitimate instruction line starting with Current time: can be overwritten; if that line appears before the previously injected runtime line, the stale injected timestamp is left untouched. This means repeated runs can still carry stale time context while also mutating user prompt content.

Useful? React with 👍 / 👎.

@jackjin1997

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review and the edge-case callout.

The concern is valid: if base contains a user-authored literal Current time: line before the previously-appended timestamp, the regex would overwrite the user's line and leave the stale one intact.

In practice this is unlikely for the callers here (appendCronStyleCurrentTimeLine is used in heartbeat-runner.ts and cron/isolated-agent/run.ts, where base is the resolved heartbeat/cron prompt text — typically system-generated, not AGENTS.md-derived content). But the concern is sound in principle.

The two strategies compared:

Strategy Duplicate Current time: lines? Overwrites user-authored line?
Always append (post-compaction-context style) Yes, on repeated runs No
Replace first match (this PR) No Yes, if user-authored line comes first

For the heartbeat/cron use case, avoiding duplicate lines (which the model would see as contradictory) is the higher priority. If it turns out that AGENTS.md-derived content flows into base for these callers, a stricter fix would be to anchor the replacement to only the last Current time: line — happy to do that if the maintainers prefer it.

@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: 88f17754b7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/current-time.ts Outdated
Comment on lines +41 to +43
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 runtime-generated lines

appendCronStyleCurrentTimeLine now replaces the first line matching Current time: anywhere in the prompt, but in heartbeat paths (src/infra/heartbeat-runner.ts and src/web/auto-reply/heartbeat-runner.ts) this helper is fed user-authored prompt text as well. If a user-defined instruction line starts with that prefix before the previously injected runtime line, this change overwrites the user instruction and leaves the stale generated timestamp later in the prompt unchanged, so repeated runs can still operate with old time context.

Useful? React with 👍 / 👎.

@jackjin1997
jackjin1997 force-pushed the fix/cron-stale-current-time branch from 88f1775 to e45ef80 Compare March 13, 2026 16:24

@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: e45ef80dea

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/current-time.ts Outdated
Comment on lines +41 to +43
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 Replace only runtime-injected timestamp lines

The new ^Current time:.*$ replacement is broad enough to rewrite user-authored prompt text, not just stale runtime metadata. In heartbeat flows, resolveHeartbeatPrompt(...) passes user-configured prompt content into appendCronStyleCurrentTimeLine (src/infra/heartbeat-runner.ts and src/web/auto-reply/heartbeat-runner.ts), so any instruction line that starts with Current time: gets silently overwritten; if that line appears before an older injected timestamp, the stale timestamp is left unchanged later in the prompt. This mutates user instructions and can still leave outdated time context in repeated runs.

Useful? React with 👍 / 👎.

@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: e5a4cd250e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/current-time.ts Outdated
Comment on lines +41 to +43
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 runtime-injected lines

The new ^Current time:.*$ replacement in appendCronStyleCurrentTimeLine rewrites the first matching line regardless of source, but heartbeat runs pass user-configured prompt text through resolveHeartbeatPrompt in src/infra/heartbeat-runner.ts; if a user instruction line starts with Current time: before the previously injected runtime line, this code overwrites user content and leaves the stale generated timestamp unchanged later in the prompt, so repeated runs can still operate on outdated time context.

Useful? React with 👍 / 👎.

@jackjin1997
jackjin1997 force-pushed the fix/cron-stale-current-time branch from e5a4cd2 to afe4874 Compare March 15, 2026 16:33

@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: afe4874149

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/current-time.ts Outdated
Comment on lines +41 to +43
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 runtime lines

appendCronStyleCurrentTimeLine now replaces the first line matching ^Current time: regardless of source, but heartbeat uses user-configured prompt text (resolveHeartbeatPrompt in src/infra/heartbeat-runner.ts:603) and then passes it into this helper (src/infra/heartbeat-runner.ts:727). If a user instruction starts with Current time: (or appears before a previously injected runtime line), this overwrites user-authored content and can leave the stale generated timestamp later in the prompt unchanged, so repeated runs can still carry outdated time context. Fresh evidence: this user-prompt passthrough path is still present in the current tree at those callsites.

Useful? React with 👍 / 👎.

@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: 7df6d31a0e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/current-time.ts Outdated
Comment on lines +41 to +43
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 Scope timestamp replacement to runtime-injected lines

existingTimeRe matches any line that begins with Current time:, so heartbeat runs can overwrite user-authored prompt content instead of only refreshing the stale runtime metadata line. Fresh evidence: resolveHeartbeatRunPrompt passes user-configured text from resolveHeartbeatPrompt(...) into appendCronStyleCurrentTimeLine(...) (src/infra/heartbeat-runner.ts:603 and src/infra/heartbeat-runner.ts:727), so if a custom prompt includes its own Current time: line before the previously injected line, user instructions are mutated and the stale injected timestamp remains later in the prompt.

Useful? React with 👍 / 👎.

@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: d2bbe5e407

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/current-time.ts Outdated
Comment on lines +41 to +43
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 Replace only runtime-injected Current time lines

The new ^Current time:.*$ match rewrites the first line with that prefix regardless of source, so heartbeat runs can overwrite user-authored prompt content instead of refreshing the stale runtime metadata line. Fresh evidence: current heartbeat flows still pass user-configured prompt text into this helper (resolveHeartbeatPrompt(...) in src/infra/heartbeat-runner.ts and extensions/whatsapp/src/auto-reply/heartbeat-runner.ts), so if a custom prompt contains Current time: before an older injected line, user instructions are mutated while the stale generated timestamp remains later in the prompt.

Useful? React with 👍 / 👎.

@jackjin1997

Copy link
Copy Markdown
Contributor Author

Regarding the repeated bot feedback about timestamp regex scope:

The ^Current time:.*$ pattern could theoretically match a user-authored line starting with "Current time:" in their heartbeat prompt. In practice this is extremely unlikely — users don't typically write lines with that exact prefix in prompts. The generated format is Current time: 2026-01-15T10:00:00+00:00 (Wednesday) which is distinctive enough.

That said, if maintainers prefer a tighter pattern, we could match the full ISO timestamp format: ^Current time: \d{4}-\d{2}-\d{2}T.*$. Happy to adjust if requested.

@jackjin1997
jackjin1997 force-pushed the fix/cron-stale-current-time branch 2 times, most recently from f5bde6b to d155db0 Compare March 15, 2026 19:28

@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: d155db051b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/current-time.ts Outdated
Comment on lines +41 to +43
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 replacement to runtime-injected timestamp lines

appendCronStyleCurrentTimeLine now replaces the first line matching ^Current time: regardless of source, but heartbeat paths pass user-authored prompt text into this helper (resolveHeartbeatPrompt(...) in src/infra/heartbeat-runner.ts and extensions/whatsapp/src/auto-reply/heartbeat-runner.ts). If a custom prompt contains its own Current time: instruction before a previously injected runtime line, this code overwrites user content and leaves the stale generated timestamp later in the prompt unchanged, so repeated runs can still execute with outdated time context.

Useful? React with 👍 / 👎.

… runs

Replace existing Current time line in-place via regex instead of
skipping injection when the prefix is already present. Prevents
stale timestamps from persisting across repeated heartbeat/cron runs.
Narrow the regex from `^Current time:.*$` to match the full
runtime-generated format ending with `YYYY-MM-DD HH:MM UTC`,
preventing accidental overwrites of user-authored lines that
start with "Current time:". Add test for the boundary case.
@jackjin1997
jackjin1997 force-pushed the fix/cron-stale-current-time branch from d155db0 to fb40ddf Compare March 16, 2026 03:15
@jackjin1997

Copy link
Copy Markdown
Contributor Author

Tightened the timestamp regex per the review feedback — now matches the full runtime format (Current time: ... YYYY-MM-DD HH:MM UTC) instead of any line starting with Current time:. Added a test verifying user-authored lines with that prefix are preserved.

@jackjin1997

Copy link
Copy Markdown
Contributor Author

Closing — no linked issue and low community engagement. Will revisit if the stale timestamp pattern becomes a reported problem.

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: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant