openclaw - 💡(How to fix) Fix Bug: message tool's after/oldest parameter silently returns [] for ISO 8601 timestamps

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…

The message tool's after parameter (and oldest, same family) silently returns an empty array for any ISO 8601 timestamp value passed as a string. No error is emitted — the tool returns {messages: []} as if no messages exist after the specified timestamp, even when many do.

Reproducible in OpenClaw 2026.5.7. Behavior is session-mode-agnostic (interactive sessions and cron-spawned sessions reproduce identically) and channel-agnostic.

Error Message

The message tool's after parameter (and oldest, same family) silently returns an empty array for any ISO 8601 timestamp value passed as a string. No error is emitted — the tool returns {messages: []} as if no messages exist after the specified timestamp, even when many do. throw new Error(Invalid 'after' value: ${JSON.stringify(value)}); The key behaviors we'd want: (a) accept ISO 8601 strings transparently, (b) error loudly on unrecognized formats instead of returning a misleading-but-non-empty result, (c) convert to Slack's epoch-seconds expectation. Several of our cron-driven skills rely on this filter to scope their reads to "today" or "since last run." Because the bug is silent (no error, just empty results), the skills returned "no work performed" while real activity existed in the target channels. Detected after-the-fact via a workbench observability layer that captured the cron output verbatim — we noticed when a daily-summary skill claimed "Quiet day, all four tracked channels had no activity" on a day with substantive discussion.

Root Cause

Hypothesis (root cause signature)

Fix Action

Fix / Workaround

Workaround we deployed: skills now call read with limit only (no after) and filter client-side by parseFloat(msg.ts) >= cutoffEpochSeconds. Works but pays in unnecessary API quota for high-volume channels.

Code Example

function parseAfter(value) {
  if (typeof value === "number") return value;
  if (typeof value === "string") {
    // ISO 8601 string
    const parsed = Date.parse(value);
    if (!isNaN(parsed)) return Math.floor(parsed / 1000); // Slack expects epoch seconds
    // Plain numeric string
    const num = Number(value);
    if (!isNaN(num)) return num;
  }
  // Reject unrecognized — don't silently fall back to a default
  throw new Error(`Invalid 'after' value: ${JSON.stringify(value)}`);
}
RAW_BUFFERClick to expand / collapse

Summary

The message tool's after parameter (and oldest, same family) silently returns an empty array for any ISO 8601 timestamp value passed as a string. No error is emitted — the tool returns {messages: []} as if no messages exist after the specified timestamp, even when many do.

Reproducible in OpenClaw 2026.5.7. Behavior is session-mode-agnostic (interactive sessions and cron-spawned sessions reproduce identically) and channel-agnostic.

Repro

Channel C0AJNB1U2DA in our Slack workspace had 5 confirmed top-level messages on 2026-05-11, with the most recent at 2026-05-11T23:12:00Z (ts=1778534021). Running the message tool from an interactive session at 2026-05-11T23:28:00Z (16 minutes after the most recent message was posted):

Call shapeReturns
{action: "read", target: "C0AJNB1U2DA", limit: 50} (no after)5 messages, newest ts=1778534021
{action: "read", target: "C0AJNB1U2DA", after: "2026-05-11T04:00:00Z"}[]
{action: "read", target: "C0AJNB1U2DA", after: "2026-05-11T00:00:00-04:00"} (TZ-aware)[]
{action: "read", target: "C0AJNB1U2DA", after: "2026-05-10T20:00:00Z"} (~24h ago)[]
{action: "read", target: "C0AJNB1U2DA", after: "1746936000"} (Unix epoch seconds as string, May 11 2025)Returns messages but from 2026-03-04 to 2026-03-10 (wrong year range) ❌

Same pattern reproduced on a second channel (C0AFK107HPZ) — all ISO 8601 forms return empty, epoch-seconds-string returns wrong-range data.

Hypothesis (root cause signature)

The wrong-year result for the epoch-seconds case (passing May 2025 epoch, getting March 2026 data back) is the smoking-gun signature of "ISO string fed to a numeric parser → NaN → silent fallback to some default range." The parser likely calls parseInt or Number(value) directly on the input without first attempting Date.parse(value) for ISO 8601 strings. ISO strings parse to NaN, the comparison falls back, and Slack's conversations.history receives either no oldest or a default value that doesn't match what the caller intended.

Fix candidate (untested):

function parseAfter(value) {
  if (typeof value === "number") return value;
  if (typeof value === "string") {
    // ISO 8601 string
    const parsed = Date.parse(value);
    if (!isNaN(parsed)) return Math.floor(parsed / 1000); // Slack expects epoch seconds
    // Plain numeric string
    const num = Number(value);
    if (!isNaN(num)) return num;
  }
  // Reject unrecognized — don't silently fall back to a default
  throw new Error(`Invalid 'after' value: ${JSON.stringify(value)}`);
}

The key behaviors we'd want: (a) accept ISO 8601 strings transparently, (b) error loudly on unrecognized formats instead of returning a misleading-but-non-empty result, (c) convert to Slack's epoch-seconds expectation.

Impact in our deployment

Several of our cron-driven skills rely on this filter to scope their reads to "today" or "since last run." Because the bug is silent (no error, just empty results), the skills returned "no work performed" while real activity existed in the target channels. Detected after-the-fact via a workbench observability layer that captured the cron output verbatim — we noticed when a daily-summary skill claimed "Quiet day, all four tracked channels had no activity" on a day with substantive discussion.

Workaround we deployed: skills now call read with limit only (no after) and filter client-side by parseFloat(msg.ts) >= cutoffEpochSeconds. Works but pays in unnecessary API quota for high-volume channels.

Environment

  • OpenClaw version: 2026.5.7
  • Deployment: ECS Fargate, headless gateway, loopback CLI
  • Channel provider: Slack

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING