Summary:
- The branch adds `useAutoCleanupTempDirTracker()`, broadens the temp-dir warning reporter to flag new manual helper imports/usages, updates docs, and migrates two script tests to the new helper.
- PR surface: Tests +301, Docs +1, Other +248. Total +550 across 8 files.
- Reproducibility: not applicable. this is test/tooling cleanup, and the changed behavior is exercised through helper/reporter tests and CI evidence rather than a user reproduction path.
Automerge notes:
- PR branch already contained follow-up commit before automerge: test: harden temp dir helper guard
- PR branch already contained follow-up commit before automerge: test: clarify auto cleanup temp dir helper name
- PR branch already contained follow-up commit before automerge: test: cover existing mkdtemp temp dir forms
- PR branch already contained follow-up commit before automerge: test: read staged temp helper source from index
Validation:
- ClawSweeper review passed for head 1fdd7d2a9a.
- Required merge gates passed before the squash merge.
Prepared head SHA: 1fdd7d2a9a
Review: https://github.com/openclaw/openclaw/pull/93209#issuecomment-4705653665
Co-authored-by: Mason Huang <masonxhuang@tencent.com>
Approved-by: hxy91819
* fix(memory-wiki): retry transient existing-page reads in wiki_apply and chatgpt import
A create_synthesis re-run and a ChatGPT conversations re-import swallowed
every existing-page read error and treated the page as brand-new, so one
transient read failure silently emptied the user's ## Notes block and
dropped hand-added frontmatter. Route both reads through a retry-once
helper that treats only a missing page as new and propagates persistent
failures, matching the ingest and imported-source fix from #98360.
* fix(memory-wiki): preserve fs-safe policy failures
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(discord): guard JSON.parse against malformed API response bodies
Wrap JSON.parse(text) in requestDiscord with try/catch to prevent a malformed Discord API response body from throwing an unhandled SyntaxError and crashing the process. On parse failure, throw a DiscordApiError with a descriptive message so the caller can handle it gracefully.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
* fix(clawsweeper): address review for automerge-openclaw-openclaw-97889 (validation-1)
* chore: trigger CI re-run for flaky checks-node-compact-small-whole-2
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
* chore: trigger CI re-run for flaky runtime-inventory-drift-check
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
* chore: trigger CI re-run for flaky checks-node-compact-large-whole-1
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
* fix(discord): guard JSON.parse against malformed API response bodies
* fix(discord): guard JSON.parse against malformed API response bodies
---------
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
* fix(agents): don't inject A2A turns into isolated-cron sessions_send (#92257)
Fire-and-forget sessions_send (timeoutSeconds === 0) with announce
delivery runs the A2A ping-pong loop. For a cross-session send
(requester != target) the loop's first iteration feeds the target
agent's reply back into the requester session as a new turn. For a
normal requester that roundtrip is intended, but for an *isolated cron*
requester it injects reply context into the isolated run and causes an
agent feedback loop.
Narrow the fix to isolated-cron requesters only (detected by a session
key containing ":cron:" or channel "cron" -- the same signal used by
src/agents/subagent-registry.ts and set in
src/cron/isolated-agent/run.ts), NOT by timeoutSeconds. Gating on
timeoutSeconds was too broad: it disabled the intended ping-pong for
normal cross-session fire-and-forget sends.
Two hunks in src/agents/tools/sessions-send-tool.ts, both reusing one
`isIsolatedCronRequester` gate:
1. Force maxPingPongTurns to 0 in the runSessionsSendA2AFlow invocation
only for an isolated-cron requester. The a2a flow's ping-pong guard
(`maxPingPongTurns > 0`) then skips the requester-injection loop and
proceeds straight to the announce step in the TARGET session,
preserving fire-and-forget announce delivery. Normal requesters keep
the configured turn count.
2. Read baselineReply for fire-and-forget sends when same-session (prior
behavior) OR isolated-cron. Without a baseline fingerprint, a2a.ts
would treat pre-existing assistant text in the target session (e.g.
an unrelated concurrent cron's output) as "the reply" and
misattribute it. The normal cross-session fire leg's history-call
count is unchanged from origin/main. Read failures are tolerated so a
snapshot error never blocks accepting the send.
Tests (sessions.test.ts): an isolated-cron cross-session fire-and-forget
forwards maxPingPongTurns: 0; a normal-requester regression guard
(discord:group:req / channel discord) forwards the configured turn count
(not 0), proving the normal ping-pong is preserved. a2a flow tests
(sessions-send-tool.a2a.test.ts): with turns=0 + requester!=target the
requester is never stepped but the target is still announced, and a
baseline-matching reply is neither injected nor announced.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agents): gate isolated-cron A2A on canonical cron-run classifier (#92257)
Address Codex [P2] review: replace the raw `:cron:` substring detector with
the canonical isCronRunSessionKey so a non-canonical cron-like requester key
(e.g. agent:main:slack:cron:job:run:uuid) keeps its intended cross-session
ping-pong. Keep the requesterChannel === "cron" arm (isolated cron runs set
that channel in src/cron/isolated-agent/run.ts). Add a regression covering the
non-canonical key.
* refactor(agents): drop dead cron-channel A2A arm, gate on canonical key only (#92257)
The requesterChannel === "cron" arm was unreachable: agentChannel is always a
DeliverableMessageChannel from resolveGatewayMessageChannel(messageProvider),
never the literal "cron". The channel: "cron" in src/cron/isolated-agent/run.ts
labels diagnostics events/lifecycle, not the tool channel. Gate on
isCronRunSessionKey alone and fix the misleading comment.
Tests drove the dead arm via agentChannel/requesterChannel "cron" plus a
non-canonical key (agent:main:cron:run:abc, which isCronRunSessionKey rejects).
Switch them to a canonical cron-run key (agent:main:cron:job:run:abc) and a
normal delivery channel so they exercise the real production gate.
* fix(agents): align cron A2A fallback baselines
* chore: prepare branch refresh
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
When keys is empty, keys[keys.length - 1] returns undefined, and the
previous ?? fallback would silently create a property with an empty
string key on the target object. Add an early return so empty keys are
a no-op instead of silently polluting the object.
* fix(gateway): distinguish reachable gateway from failed status probe
* fix(status): gate owns-port RPC recovery guidance on no stale gateway PIDs
inspectGatewayRestart can set health.healthy from bare reachability after
ownership attribution failed, while still returning a non-empty
staleGatewayPids. Printing owns-port recovery guidance in that case
contradicted the dedicated stale-PID diagnostic below it. Require
staleGatewayPids to be empty before treating healthy as owns-port proof.
* feat(cli): openclaw attach — launch Claude Code bound to a gateway session with scoped MCP tools
* fix(cli): use token-only MCP config for attach
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* Diagnose Windows LAN Gateway firewall blocks
* Fix Windows firewall diagnostic lint
* fix: gate gateway firewall diagnostics to local targets
* fix: keep firewall inspection off critical flows