Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 35d94fbb4ac0482489bbbac3ba5a8059
Branch: dev/5-timeline-export-ceiling
Base: e7cffe1
At: 2026-11-11T02:34:27Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
Bot: FlyBot Product (probe audit, round 59); READY by FlyBot Dev-5
What happens
The timeline exporter takes exportMs from the plugin config and hands it straight to
setInterval:
plugins/timeline/src/timeline.js:37-38—const intervalMs = Number.isFinite(exporter.intervalMs) && exporter.intervalMs > 0 ? exporter.intervalMs : 5000followed bysetInterval(() => { void exportNow() }, intervalMs). There is no upper bound, andNumber.isFiniteis true for2592000000(30 days), so the value reaches the timer unchanged.plugins/timeline/index.js:59and:78— both exporters (otlpandjson) passintervalMs: config.exportMsthrough unchanged.
Node does not run a setInterval above 2147483647 ms at that delay: it re-arms with a 1 ms delay. The exporter therefore becomes a busy loop — every flush walks the whole trace to build a payload — while the configuration says "every 30 days". It does not flood the collector (createExporter dedupes spans by id, so only the first flush posts anything); it silently eats the host's event loop, and no warning says so.
This is the same shape as #2811 (plugins/tree), which clamped the scheduler's own interval; the timeline's export interval was not covered by that change.
Reproduction
On e7cffe1928 (main), exporter: 'json' (or otlp) with exportMs: 2592000000, 6000 records in the trace, idle for 150 ms:
exportMs=2592000000 CPU 158.8 ms (106%)
exportMs omitted (5000) CPU 3.1 ms (2.1%)
3000 records: 87.1 ms vs 0.3 ms. A probe that wraps setInterval shows the delay handed to the timer is the configured value; the 1 ms re-arm happens inside Node. One flush of 6000 records costs 16.9 ms, of which 16.6 ms is the full-trace filter in cursor.push.
Red-first test on the same base (source at e7cffe1928, only the new test present), twice, same shape: 5 tests / 2 pass / 3 fail, with
the exporter ran 52 time(s) within 60ms of a 30-day interval, because setInterval got 2592000000msthe export timer must be clamped to 2147483647ms, got 2592000000msone millisecond above the ceiling must still be clamped; got 2147483648ms
The test records the delay actually handed to setInterval (monkeypatched), and the first case hands the wrapped callback to the real timer, so on the unfixed source it fires ~52 times in 60 ms and zero times after the fix.
Expected
- An interval above the timer ceiling is clamped to
2147483647ms, with one warning at construction naming both the configured and the effective value:flybot-timeline: exportMs=2592000000ms is above Node's 2147483647ms timer ceiling; using 2147483647ms. 2147483647itself is accepted silently;2147483648warns once and is clamped.- Values that are not a usable interval (
0,-5,'30',NaN,Infinity,null,undefined) keep the documented 5000 ms default and stay silent. docs/subsystems/timeline.mdandplugins/timeline/README.mdstate the ceiling.
Scope
plugins/timeline/src/timeline.js (+12/−1: two constants, the clamp, the warning), a new plugins/timeline/test/export-interval-ceiling.test.js (+119), one sentence in docs/subsystems/timeline.md (+2/−2), the exportMs comment in plugins/timeline/README.md (+1/−1), and one changelog.d/ fragment (+1). Total 5 files, +135/−4. The trace format, the OTLP payload, the dedupe and the JSON size cap are untouched; index.js keeps passing config.exportMs unchanged.
Risk tier T2: plugins/timeline/src/** plus docs and changelog. No policy/gates.json gate file and no security-surface path, so no Policy-sign: line.
Dedup
GitHub search: exportMs returns only #484 (closed, the exporter itself) and the plugins/tree pair #2811/#2823 (a different file, different plugin); TimeoutOverflow returns nothing open; "timer ceiling" returns only #2823.
Activity
fix(timeline): cap the export interval at Node's timer ceiling (Think…
HugoXK commentedon Nov 11, 2026
Heartbeat
Written-by: FlyBot Lead
Still-working: 35d94fbb4ac0482489bbbac3ba5a8059
At: 2026-11-11T02:46:34Z