Skip to content

Commit 3f7fff6

Browse files
author
1bcMax
committed
fix: strip OpenClaw timestamps before hashing for dedup
OpenClaw injects timestamps like [SUN 2026-02-07 13:30 PST] at the start of messages. When OpenClaw retries a request after timeout, the new timestamp causes different hash, bypassing dedup and resulting in duplicate Discord messages. Strips timestamp prefixes from message content before canonicalizing and hashing to ensure requests with same content hash identically.
1 parent 49a4b29 commit 3f7fff6

3 files changed

Lines changed: 36 additions & 5 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@blockrun/clawrouter",
3-
"version": "0.3.33",
3+
"version": "0.3.34",
44
"description": "Smart LLM router — save 78% on inference costs. 30+ models, one wallet, x402 micropayments.",
55
"type": "module",
66
"main": "dist/index.js",

src/dedup.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,34 @@ function canonicalize(obj: unknown): unknown {
4040
return sorted;
4141
}
4242

43+
/**
44+
* Strip OpenClaw-injected timestamps from message content.
45+
* Format: [DAY YYYY-MM-DD HH:MM TZ] at the start of messages.
46+
* Example: [SUN 2026-02-07 13:30 PST] Hello world
47+
*
48+
* This ensures requests with different timestamps but same content hash identically.
49+
*/
50+
const TIMESTAMP_PATTERN = /^\[\w{3}\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+\w+\]\s*/;
51+
52+
function stripTimestamps(obj: unknown): unknown {
53+
if (obj === null || typeof obj !== "object") {
54+
return obj;
55+
}
56+
if (Array.isArray(obj)) {
57+
return obj.map(stripTimestamps);
58+
}
59+
const result: Record<string, unknown> = {};
60+
for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
61+
if (key === "content" && typeof value === "string") {
62+
// Strip timestamp prefix from message content
63+
result[key] = value.replace(TIMESTAMP_PATTERN, "");
64+
} else {
65+
result[key] = stripTimestamps(value);
66+
}
67+
}
68+
return result;
69+
}
70+
4371
export class RequestDeduplicator {
4472
private inflight = new Map<string, InflightEntry>();
4573
private completed = new Map<string, CachedResponse>();
@@ -51,11 +79,14 @@ export class RequestDeduplicator {
5179

5280
/** Hash request body to create a dedup key. */
5381
static hash(body: Buffer): string {
54-
// Canonicalize JSON to ensure consistent hashing regardless of field order
82+
// Canonicalize JSON to ensure consistent hashing regardless of field order.
83+
// Also strip OpenClaw-injected timestamps so retries with different timestamps
84+
// still match the same dedup key.
5585
let content = body;
5686
try {
5787
const parsed = JSON.parse(body.toString());
58-
const canonical = canonicalize(parsed);
88+
const stripped = stripTimestamps(parsed);
89+
const canonical = canonicalize(stripped);
5990
content = Buffer.from(JSON.stringify(canonical));
6091
} catch {
6192
// Not valid JSON, use raw bytes

0 commit comments

Comments
 (0)