Skip to content

fix(gateway): bound busy channel health by real run age#103793

Merged
fuller-stack-dev merged 7 commits into
openclaw:mainfrom
yetval:fix/channel-health-busy-run-age
Jul 19, 2026
Merged

fix(gateway): bound busy channel health by real run age#103793
fuller-stack-dev merged 7 commits into
openclaw:mainfrom
yetval:fix/channel-health-busy-run-age

Conversation

@yetval

@yetval yetval commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

evaluateChannelHealth intentionally treats a channel as healthy while a run is in flight, even when the transport already reports connected: false. That busy override is meant to be bounded by a 25 minute stale ceiling, but the ceiling is measured from lastRunActivityAt, which the run-state heartbeat refreshes every 60 seconds for as long as any run is active.

If a per-message task hangs forever, for example a channel send blocking on a half-open or dead socket with no send timeout after the transport disconnected, onRunEnd never fires, the heartbeat keeps ticking, and lastRunActivityAt never ages past roughly 60 seconds. The >= 25min stuck branch, the only escape from busy-overrides-disconnected, becomes permanently unreachable. The account is then reported healthy forever by the channel health monitor (if (health.healthy) continue), the readiness probe (src/gateway/server/readiness.ts), and the openclaw health CLI (src/commands/health.ts). No restart fires and no operator-visible signal appears until the process is restarted. Transport disconnects mid-send with no send timeout are a normal production failure mode.

Why This Change Was Made

When the transport already reports disconnected, the busy override must be bounded by how long the run has actually been in flight, not by a timestamp the heartbeat keeps fresh, so a single hung run eventually breaches the stale ceiling. When the transport is healthy the channel queue's no-run-timeout contract holds: a legitimate run may take arbitrarily long and must never be aged out by its start time.

Root cause, src/gateway/channel-health-policy.ts busy branch (before):

const runActivityAge =
  lastRunActivityAt == null
    ? Number.POSITIVE_INFINITY
    : Math.max(0, policy.now - lastRunActivityAt);
if (runActivityAge < BUSY_ACTIVITY_STALE_THRESHOLD_MS) {
  return { healthy: true, reason: "busy" };
}
return { healthy: false, reason: "stuck" };

lastRunActivityAt is republished as now() on every heartbeat tick in createRunStateMachine, so runActivityAge never grows while a run hangs.

Fix. The channel run queue now tracks each in-flight run's start time keyed by an opaque run handle returned from onRunStart and passed back to onRunEnd, and publishes the oldest still-active run's start as activeRunStartedAt. When the oldest run ends the reported start advances to the next-oldest, and it clears when no run is active. The health policy busy branch applies the run-age term only while the snapshot reports connected: false (after):

const runActivityAge =
  lastRunActivityAt == null
    ? Number.POSITIVE_INFINITY
    : Math.max(0, policy.now - lastRunActivityAt);
const disconnectedRunStartAge =
  snapshot.connected === false && activeRunStartedAt != null
    ? Math.max(0, policy.now - activeRunStartedAt)
    : 0;
const busyAge = Math.max(runActivityAge, disconnectedRunStartAge);
if (busyAge < BUSY_ACTIVITY_STALE_THRESHOLD_MS) {
  return { healthy: true, reason: "busy" };
}
return { healthy: false, reason: "stuck" };

Because the oldest active run's start is fixed while that run is in flight and the heartbeat only advances lastRunActivityAt, a run hanging past the threshold while the transport reports disconnected now reports stuck. When the transport is connected, or the channel does not report transport state, the run-age term is not applied and the ceiling stays keyed to lastRunActivityAt exactly as on main, so long legitimate runs on a healthy transport are never treated as stuck. Because the reported start is the oldest active run and advances as runs complete, a channel churning through many short overlapping runs stays healthy; only a genuinely hung run masking a disconnect breaches the ceiling. The activeRunStartedAt field flows through the same run-state to snapshot path as lastRunActivityAt (run-state-machine.ts publish, account-snapshot-fields.ts read, types.core.ts snapshot type), so all three consumers of evaluateChannelHealth (monitor, readiness, CLI) get the corrected verdict.

Why This Is The Right Boundary

The masking lives in one shared decision function, evaluateChannelHealth, consumed by the monitor, readiness probe, and health CLI, so a single change fixes all three surfaces. The heartbeat that defeats the ceiling lives in createRunStateMachine, the one owner of run activity status, so the run-age fact is surfaced where it is produced rather than re-derived per caller. createChannelRunQueue shares one run-state machine across all queue keys and KeyedAsyncQueue runs unrelated keys concurrently, so activeRuns routinely exceeds 1; tracking the oldest active run rather than a busy-streak start keeps a continuously busy account healthy while still catching a single hung run. Legitimate busy behavior is preserved: short, active, and overlapping runs stay healthy, and snapshots without a start time fall back to the previous lastRunActivityAt behavior. onRunEnd now takes the run handle returned by onRunStart; the only in-repo caller, createChannelRunQueue, is migrated in this same commit, and typed plugin consumers get a compile error rather than silent breakage. Restoring a no-argument onRunEnd overload was deliberately avoided because it would reintroduce the counter-decrement accounting this change removes and split run tracking across two paths. The 25 minute ceiling is the pre-existing BUSY_ACTIVITY_STALE_THRESHOLD_MS constant; this change makes it reachable only for the case it was meant to bound, busy masking a transport that already reported disconnected. The queue's no-run-timeout contract is preserved: connected runs, and runs on channels that do not report transport state, are never aged out by their start time. This is distinct from PR #96198, which fixes the same class of heartbeat-hides-stuck bug in the agent diagnostic stuck-session detector (src/agents/*, src/logging/diagnostic-run-activity.ts), a different subsystem that does not touch channel health.

User Impact

Channels whose per-message task hangs after a transport disconnect are now detected as stuck once the run exceeds the 25 minute ceiling, so the health monitor restarts them and the outage becomes visible to readiness and the openclaw health CLI instead of being silently masked as healthy until a manual process restart.

Evidence

  • Regression tests in src/gateway/channel-health-policy.test.ts: a disconnected channel with a run in flight longer than the threshold and a fresh heartbeat evaluates to { healthy: false, reason: "stuck" } (fails on pristine main, which returns busy); a connected run past the threshold with a fresh heartbeat stays busy; a run past the threshold on a channel that does not report connected stays busy; a short-lived run stays busy.
  • Tests in src/plugin-sdk/channel-lifecycle.queue.test.ts: with two concurrent runs, activeRunStartedAt stays pinned to the oldest run's start while a newer run completes, advances to the next-oldest start when the oldest run ends, and clears to null when no run is active.
  • Verification commands (from a deps-enabled checkout):
    • node scripts/run-vitest.mjs src/gateway/channel-health-policy.test.ts src/plugin-sdk/channel-lifecycle.queue.test.ts passes on this patch.
    • The disconnected stuck assertion fails against pristine origin/main source.
    • oxlint and oxfmt --check are clean on the changed files; tsgo is clean on the changed surfaces.

Real behavior proof

Behavior addressed: a disconnected channel whose per-message run hangs forever is reported healthy forever, because the run-state heartbeat keeps refreshing the activity timestamp that the 25 minute busy ceiling is measured against, so the health monitor never restarts it.
Real environment tested: drove the real periodic health monitor loop from startChannelHealthMonitor (src/gateway/channel-health-monitor.ts) over the real evaluateChannelHealth (src/gateway/channel-health-policy.ts) fed by a live createRunStateMachine (src/channels/run-state-machine.ts) with its real setInterval heartbeat, on real wall-clock (Date.now), on pristine main 091584b and on PR head 21284cf. Only the channel transport was stubbed, at the narrowest seam (the ChannelManager the monitor calls), so the monitor's real restart recovery path stays observable. Executed on a remote Crabbox AWS lease (c7a.xlarge, eu-west-1, lease cbx_452e9479c6de), not on the author machine. The 25 minute busy ceiling and the 60 second heartbeat were both scaled down 300x (to 5 seconds and 200 milliseconds) so a genuinely hung run breaches the ceiling in seconds of real elapsed time rather than 26 minutes; nothing else was altered and both runs used identical scaling.
Exact steps or command run after this patch: a run begins via the real run-state machine and then hangs (its end callback never fires); the real monitor evaluates the live snapshot every 250 milliseconds while the transport stays connected:false; the harness prints the live snapshot ages and health verdict once per second and the monitor's real restart actions as they occur.
Evidence after fix:

# BEFORE (pristine main 091584b, run hung well past the scaled ceiling)
[health-monitor] started (interval: 0s, startup-grace: 0s, channel-connect-grace: 3600s)
t=1.0s connected=false activeRuns=1 lastRunActivityAtAge=0.2s activeRunStartedAtAge=n/a -> healthy=true reason=busy
t=5.0s connected=false activeRuns=1 lastRunActivityAtAge=0.2s activeRunStartedAtAge=n/a -> healthy=true reason=busy
t=7.0s connected=false activeRuns=1 lastRunActivityAtAge=0.2s activeRunStartedAtAge=n/a -> healthy=true reason=busy
SUMMARY monitorRestarts=0 finalVerdict=busy
# AFTER (PR head 21284cf, identical inputs, identical 300x scaling)
[health-monitor] started (interval: 0s, startup-grace: 0s, channel-connect-grace: 3600s)
t=1.0s connected=false activeRuns=1 lastRunActivityAtAge=0.2s activeRunStartedAtAge=1.0s -> healthy=true reason=busy
t=4.0s connected=false activeRuns=1 lastRunActivityAtAge=0.2s activeRunStartedAtAge=4.0s -> healthy=true reason=busy
t=5.0s connected=false activeRuns=1 lastRunActivityAtAge=0.2s activeRunStartedAtAge=5.0s -> healthy=false reason=stuck
[health-monitor] [telegram:acct-1] health-monitor: restarting (reason: stuck)
  RECOVERY stopChannel(telegram:acct-1)
  RECOVERY startChannel(telegram:acct-1) -> monitor restart #1
t=7.0s connected=false activeRuns=1 lastRunActivityAtAge=0.2s activeRunStartedAtAge=7.0s -> healthy=false reason=stuck
SUMMARY monitorRestarts=5 finalVerdict=stuck

Observed result after fix: on identical live input, the heartbeat pins lastRunActivityAt age at 0.2s in both runs, so BEFORE the channel stays healthy and busy for the whole hang and the monitor never restarts it (monitorRestarts=0); AFTER, the fixed activeRunStartedAt age grows until it breaches the ceiling, the verdict flips to unhealthy and stuck, and the real health monitor completes its restart recovery path (stopChannel then startChannel) five times over the window. The one value that moves from wrong to right is the health verdict for a hung disconnected channel, and with it the monitor's restart action.
What was not tested: no live provider socket was hung end to end; the hang was a real never-resolving run under the real heartbeat, and the transport was a stub at the ChannelManager seam. The busy ceiling and heartbeat were scaled 300x on real wall-clock so the breach happens in seconds; the unscaled 25 minute breach was not waited out. Full build not run; the change is not on a lazy or packaging boundary.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S labels Jul 10, 2026
@yetval
yetval force-pushed the fix/channel-health-busy-run-age branch from 3e2d4a2 to fdac98d Compare July 10, 2026 16:15
@yetval
yetval marked this pull request as ready for review July 10, 2026 16:22
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 10, 2026
@clawsweeper

clawsweeper Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 18, 2026, 9:21 PM ET / July 19, 2026, 01:21 UTC.

Summary
The PR records the oldest active channel-run start time in account snapshots and uses it to bound the healthy-busy override only when a transport reports connected: false.

PR surface: Source +41, Tests +125. Total +166 across 7 files.

Reproducibility: yes. from source: a live run-state heartbeat refreshes lastRunActivityAt, so current-main busy logic cannot age out a hung run that remains active; the added regression case isolates that condition. The reviewer did not independently execute the runtime reproduction in this read-only review.

Review metrics: none identified.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🐚 platinum hermit
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Post redacted current-head monitor output for disconnected stale-run recovery and connected long-run preservation.
  • If updating the PR body does not trigger a new review, ask a maintainer to comment @clawsweeper re-review.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The posted live monitor transcript is useful but targets 21284cf; the latest a1af976 disconnected-only safeguard lacks posted current-head output for both the disconnected recovery path and connected long-run control. Redact private endpoints, IPs, tokens, and account details in the added evidence. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P0] The latest change can trigger health-monitor channel restarts after the stale threshold; without current-head runtime proof of both controls, a regression in the disconnected-only gate could either continue masking an outage or restart a legitimately busy channel.

Maintainer options:

  1. Add current-head monitor controls (recommended)
    Post redacted live output from a1af976 showing both the disconnected restart path and the connected long-run no-restart control before merge.
  2. Accept the remaining proof gap
    Merge based on focused tests and CI while explicitly accepting that the latest runtime guard lacks posted current-head behavior evidence.

Next step before merge

  • [P1] The only remaining merge gate is contributor-provided current-head runtime proof; an automated repair branch cannot demonstrate the contributor’s live monitor setup.

Security
Cleared: The patch changes in-process lifecycle accounting and health evaluation only; no dependency, workflow, secret, permission, artifact, or supply-chain surface is introduced.

Review details

Best possible solution:

Keep the disconnected-only age bound, then add redacted current-head monitor output showing a hung disconnected run becomes stuck and a connected long-running control remains busy under the same live heartbeat setup.

Do we have a high-confidence way to reproduce the issue?

Yes, from source: a live run-state heartbeat refreshes lastRunActivityAt, so current-main busy logic cannot age out a hung run that remains active; the added regression case isolates that condition. The reviewer did not independently execute the runtime reproduction in this read-only review.

Is this the best way to solve the issue?

Yes, conditionally: surfacing the oldest queue-owned run start and consuming it in the shared health policy is the narrowest shared fix, and the disconnected-only guard preserves the established no-run-timeout contract. Current-head real behavior proof is still required to validate that boundary before merge.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against c3adaa3195bd.

Label changes

Label changes:

  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🐚 platinum hermit.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🦪 silver shellfish, so this older rating label is no longer current.

Label justifications:

  • P1: A disconnected channel with a hung send can remain falsely healthy and prevent the monitor from recovering a real channel outage.
  • merge-risk: 🚨 availability: The patch changes when the health monitor declares busy channels stuck and restarts their channel runtime.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The posted live monitor transcript is useful but targets 21284cf; the latest a1af976 disconnected-only safeguard lacks posted current-head output for both the disconnected recovery path and connected long-run control. Redact private endpoints, IPs, tokens, and account details in the added evidence. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +41, Tests +125. Total +166 across 7 files.

View PR surface stats
Area Files Added Removed Net
Source 5 48 7 +41
Tests 2 126 1 +125
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 7 174 8 +166

What I checked:

  • Current safeguard: The changed health policy applies the fixed run-age term only when snapshot.connected === false, preserving the existing heartbeat-based busy behavior for connected or transport-state-unknown channels. (src/gateway/channel-health-policy.ts:105, a1af976ae51d)
  • Regression coverage: The patch adds explicit unit coverage for disconnected stale runs becoming stuck, while connected and transport-state-unknown long runs with fresh heartbeats remain busy. (src/gateway/channel-health-policy.test.ts:89, a1af976ae51d)
  • Concurrent-run accounting: The queue tests cover retaining the oldest tracked run start, advancing it when that run completes, and clearing it when no tracked runs remain. (src/plugin-sdk/channel-lifecycle.queue.test.ts:73, a1af976ae51d)
  • Maintainer review follow-up: A reviewer identified that the earlier form would restart a connected channel after a legitimate long run; the latest commit is specifically the connected === false safeguard requested in that review. (src/gateway/channel-health-policy.ts:105, a1af976ae51d)
  • Proof gap at current head: The PR body’s live monitor transcript compares main with 21284cf, while the material connected-transport safeguard landed later in a1af976; the author said refreshed current-head disconnected and connected proof would follow, but no such current-head transcript is present in the supplied discussion. (a1af976ae51d)

Likely related people:

  • fuller-stack-dev: Their current review identified the no-run-timeout invariant and specified the disconnected-only guard and regression cases implemented by the latest commit. (role: reviewer and adjacent behavior owner; confidence: medium; files: src/gateway/channel-health-policy.ts, src/plugin-sdk/channel-lifecycle.queue.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (11 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-10T19:28:13.827Z sha bd8c71b :: needs changes before merge. :: [P1] Keep tracked run handles out of the public SDK return type
  • reviewed 2026-07-10T19:52:38.927Z sha bd8c71b :: needs changes before merge. :: [P1] Keep tracked run handles out of the public SDK return type
  • reviewed 2026-07-10T20:22:46.934Z sha e157eaa :: needs changes before merge. :: [P2] Wrap the returned isActive method
  • reviewed 2026-07-10T20:29:11.013Z sha e157eaa :: needs changes before merge. :: [P2] Wrap the returned isActive method
  • reviewed 2026-07-10T22:25:05.933Z sha d5f25fa :: needs maintainer review before merge. :: none
  • reviewed 2026-07-10T22:33:41.266Z sha d5f25fa :: needs maintainer review before merge. :: none
  • reviewed 2026-07-10T22:39:30.266Z sha d5f25fa :: needs maintainer review before merge. :: none
  • reviewed 2026-07-19T00:38:44.059Z sha a1af976 :: needs real behavior proof before merge. :: none

@yetval

yetval commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Refreshed the Real behavior proof to address the prior proof P1s (injected clock, no real recovery path shown). The proof now drives the real periodic health monitor loop (startChannelHealthMonitor) over the real evaluateChannelHealth fed by a live createRunStateMachine with its real setInterval heartbeat, on real wall-clock, on a remote Crabbox AWS lease (c7a.xlarge, eu-west-1). Only the channel transport is stubbed at the ChannelManager seam, so the monitor's real restart recovery path stays observable.

On identical live input, pinned to head fdac98d vs pristine main 091584b:

  • BEFORE: the heartbeat pins the activity age near zero, the hung disconnected channel stays healthy and busy for the whole hang, and the monitor never restarts it (monitorRestarts=0).
  • AFTER: the fixed run-start age grows past the ceiling, the verdict flips to unhealthy and stuck, and the real monitor completes its restart recovery path (stopChannel then startChannel) repeatedly.

The 25 minute busy ceiling and 60 second heartbeat were both scaled down 300x on real elapsed time so the breach happens in seconds rather than 26 minutes; identical scaling on both runs. Full block is in the PR body under Real behavior proof.

@clawsweeper

clawsweeper Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 10, 2026
@yetval
yetval force-pushed the fix/channel-health-busy-run-age branch from fdac98d to 21284cf Compare July 10, 2026 18:11
@yetval

yetval commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed the rank-up move on lifecycle compatibility. onRunEnd() remains source-compatible for released zero-argument plugin consumers, while the internal channel queue passes its additive run handle so concurrent runs still retire exactly. Added coverage for the zero-argument callback path and retained the overlapping-run case. Rebased onto current main at 5bb5e4f and refreshed the proof head to 21284cf.

Focused lifecycle, gateway health-policy, and SDK subpath suites pass; scoped formatting and core type checks are clean.

@clawsweeper

clawsweeper Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@yetval
yetval force-pushed the fix/channel-health-busy-run-age branch from 21284cf to bd8c71b Compare July 10, 2026 19:00
@yetval

yetval commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

The current head separates anonymous zero-argument lifecycle accounting from handle-aware run-age tracking. The shared queue retains exact handles; legacy concurrent completions retain busy counts but never publish a guessed active-run start. Added coverage for anonymous concurrency and both exact completion orders. All GitHub checks are green on bd8c71b.

@clawsweeper

clawsweeper Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@yetval

yetval commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-run

@clawsweeper

clawsweeper Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jul 10, 2026
@clawsweeper clawsweeper Bot added the rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. label Jul 10, 2026
yetval added 6 commits July 10, 2026 22:09
The channel health policy treats a channel as healthy-busy even while
disconnected, bounded only by a 25 minute stale ceiling measured from
lastRunActivityAt. The run-state heartbeat refreshes lastRunActivityAt
every 60 seconds for as long as any run is active, so a run that hangs
forever (for example a send blocking on a dead socket after the
transport already reported connected:false) keeps that timestamp fresh
and the stuck ceiling is never reached. The account is then reported
healthy forever by the health monitor, readiness probe, and health CLI,
and no restart ever fires.

createRunStateMachine now tracks each in-flight run's start time keyed by
an opaque run handle and publishes the oldest still-active run's start as
activeRunStartedAt. The health policy busy override keys its ceiling off
the real run age, so a run stuck longer than the threshold reports stuck
and the monitor can restart it. Because the reported start is the oldest
active run and advances to the next-oldest as runs complete, a channel
churning through many short overlapping runs (activeRuns above 1 across
concurrent queue keys) stays healthy; only a genuinely hung run breaches
the ceiling. Short and active runs stay healthy and the existing
lastRunActivityAt fallback is preserved for snapshots without a start
time.
Keep the released zero-argument onRunEnd callback source-compatible while allowing internal queue callers to pass a run handle for exact concurrent-run accounting. The compatibility path closes the oldest active run, preserving existing lifecycle behavior for consumers that do not use handles.
The zero-argument lifecycle callbacks cannot identify which concurrent run completed, so they must not update the identity-sensitive run start used by channel health. Keep their busy count separately and reserve exact start tracking for the shared queue's handle-aware lifecycle path.
Keep the public run-state lifecycle callbacks unchanged. The channel queue now owns opaque run identity and augments its status updates with the oldest active queue run, so implementation details do not expand the SDK surface.
Keep activeRunStartedAt in the internal status patch type so the queue can publish its private tracked-run age through the existing status sink.
@yetval
yetval force-pushed the fix/channel-health-busy-run-age branch from e157eaa to d5f25fa Compare July 10, 2026 22:19
@yetval

yetval commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed the remaining P2 patch-quality finding. src/plugin-sdk/channel-lifecycle.core.ts now returns isActive: () => runState.isActive() instead of copying the unbound method reference, clearing the typescript(unbound-method) violation on the changed line that was failing the check-additional-runtime-topology-architecture lane. Private queue-owned run tracking, the existing 25 minute ceiling, and the public SDK callbacks are unchanged.

Refreshed onto current origin/main (42c80df). Exact-head validation on d5f25fa:

  • node scripts/run-oxlint.mjs src/plugin-sdk/channel-lifecycle.core.ts clean (type-aware, exit 0; the prior unbound-method error is gone).
  • oxfmt --check src/plugin-sdk/channel-lifecycle.core.ts clean.
  • node scripts/run-tsgo.mjs clean, no type errors.
  • node scripts/run-vitest.mjs src/plugin-sdk/channel-lifecycle.queue.test.ts src/plugin-sdk/channel-lifecycle.test.ts src/gateway/channel-health-policy.test.ts src/channels/run-state-machine.test.ts passes (102 tests), covering both hung-oldest-run detection and healthy overlapping short-run behavior.

@clawsweeper

clawsweeper Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 10, 2026
@fuller-stack-dev

Copy link
Copy Markdown
Member

Blocking correctness issue: activeRunStartedAt is applied to every busy channel before transport health is checked. On the current head, a connected Discord account with a fresh transport heartbeat and a legitimate 26-minute active run evaluates as stuck; the health monitor then stops/restarts the channel. That conflicts with the channel queue's explicit no-run-timeout contract and Discord's deliberate removal of channel-owned run timeouts.

Please constrain the run-age ceiling to cases where busy is masking an independently unhealthy transport condition (at minimum, connected === false). Please also add regression coverage proving:

  • a connected run older than 25 minutes remains healthy/busy;
  • a disconnected old run becomes stuck;
  • when the oldest concurrent run ends, activeRunStartedAt advances to the next-oldest run.

@yetval

yetval commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

@fuller-stack-dev Agreed. As pushed, the ceiling applied run age to healthy transports, which conflicts with the queue's no-run-timeout contract. Fixed in a1af976.

The busy branch now derives the run-age term only when the snapshot reports connected === false (src/gateway/channel-health-policy.ts:108). For connected transports, and for channels that do not report transport state at all, the ceiling stays keyed to lastRunActivityAt exactly as on main, so a legitimate 26-minute run with a fresh heartbeat evaluates healthy/busy indefinitely. The run-age term only bounds the case the PR targets: busy masking a transport that already reported disconnected.

Regression coverage for the three cases you listed:

  • connected run 26 minutes past its start with a fresh heartbeat stays healthy/busy: src/gateway/channel-health-policy.test.ts:101, plus the same shape with connected unreported at src/gateway/channel-health-policy.test.ts:113
  • disconnected run 26 minutes past its start with a fresh heartbeat evaluates stuck: src/gateway/channel-health-policy.test.ts:89
  • when the oldest of two concurrent runs ends, activeRunStartedAt advances to the next-oldest run's start: src/plugin-sdk/channel-lifecycle.queue.test.ts:126 (the companion case where the newer run ends first and the oldest start is retained is directly above it)

Refreshed real behavior proof against this head, including a live connected-long-run control, will follow in the PR body shortly.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Jul 19, 2026
@fuller-stack-dev

Copy link
Copy Markdown
Member

Land-ready proof refreshed for exact head a1af976ae51d06aa17290dd70b919e3e37b330ae against canonical main 68771ebdfef121191bbe8762d622418083f3cfa8.

  • Sanitized direct AWS Crabbox (public network, no IAM role, no Tailscale, no hydration): pnpm test src/gateway/channel-health-policy.test.ts src/plugin-sdk/channel-lifecycle.queue.test.ts extensions/discord/src/monitor/message-handler.queue.test.ts — 124 assertions passed (run).
  • Sanitized direct AWS Crabbox: pnpm check:changed with the documented remote-child flags — core/extension typechecks, lint, import-cycle, and boundary guards passed (run).
  • Required openclaw/ci-gate passes; current-main synthetic merge and git diff --check pass.
  • Trusted autoreview --mode branch --base origin/main completed. Its sole P2 was rejected after tracing the lifecycle contract: deactivation intentionally emits no status, stopped snapshots bypass busy evaluation, inherited busy state falls through startup grace, and each replacement queue initializes activeRunStartedAt: null.

Behavior proof now covers the requested boundary: connected or transport-unknown long runs remain healthy/busy; an explicitly disconnected old run becomes stuck; concurrent completion advances to the next-oldest run. Known gap: no live provider socket was held hung for 25 real minutes; the changed decision and monitor action are covered deterministically.

@fuller-stack-dev
fuller-stack-dev merged commit 18b79d9 into openclaw:main Jul 19, 2026
152 of 162 checks passed
@fuller-stack-dev

Copy link
Copy Markdown
Member

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 19, 2026
)

* fix(gateway): bound busy channel health by real run age

The channel health policy treats a channel as healthy-busy even while
disconnected, bounded only by a 25 minute stale ceiling measured from
lastRunActivityAt. The run-state heartbeat refreshes lastRunActivityAt
every 60 seconds for as long as any run is active, so a run that hangs
forever (for example a send blocking on a dead socket after the
transport already reported connected:false) keeps that timestamp fresh
and the stuck ceiling is never reached. The account is then reported
healthy forever by the health monitor, readiness probe, and health CLI,
and no restart ever fires.

createRunStateMachine now tracks each in-flight run's start time keyed by
an opaque run handle and publishes the oldest still-active run's start as
activeRunStartedAt. The health policy busy override keys its ceiling off
the real run age, so a run stuck longer than the threshold reports stuck
and the monitor can restart it. Because the reported start is the oldest
active run and advances to the next-oldest as runs complete, a channel
churning through many short overlapping runs (activeRuns above 1 across
concurrent queue keys) stays healthy; only a genuinely hung run breaches
the ceiling. Short and active runs stay healthy and the existing
lastRunActivityAt fallback is preserved for snapshots without a start
time.

* fix(channels): retain run-state callback compatibility

Keep the released zero-argument onRunEnd callback source-compatible while allowing internal queue callers to pass a run handle for exact concurrent-run accounting. The compatibility path closes the oldest active run, preserving existing lifecycle behavior for consumers that do not use handles.

* fix(channels): keep anonymous runs out of age tracking

The zero-argument lifecycle callbacks cannot identify which concurrent run completed, so they must not update the identity-sensitive run start used by channel health. Keep their busy count separately and reserve exact start tracking for the shared queue's handle-aware lifecycle path.

* fix(channels): keep tracked runs internal

Keep the public run-state lifecycle callbacks unchanged. The channel queue now owns opaque run identity and augments its status updates with the oldest active queue run, so implementation details do not expand the SDK surface.

* fix(channels): type queue run start status

Keep activeRunStartedAt in the internal status patch type so the queue can publish its private tracked-run age through the existing status sink.

* fix(channels): wrap isActive to satisfy unbound-method lint

* fix(gateway): gate busy run-age ceiling on disconnected transport
RomneyDa added a commit that referenced this pull request Jul 22, 2026
* fix: gate diagnostics command to owners

(cherry picked from commit 170bf72)

* fix(agent): replace self-wait with deferred release in retained-lock abort cleanup (#96100)

* fix(agent): wait for retained session write before releasing held lock on abort

* fix(agent): replace self-wait with deferred release in retained-lock abort cleanup

* fix(test): reject fallback acquire with SessionWriteLockTimeoutError in active-scope cleanup test

* fix(agent): trim retained-lock comments

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: sallyom <[email protected]>
(cherry picked from commit 0a042f6)

* fix(gateway): resume channel after pending task recovery

(cherry picked from commit 6039da3)

* fix(gateway): resume channel after pending task recovery

(cherry picked from commit ecd29fe)

* fix(outbound): ignore empty delivery receipts (#79811)

(cherry picked from commit 9a735be)

* fix(agents): guard delivery-evidence attachment recursion against cycles (#97041)

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

* fix(agents): guard delivery-evidence attachment recursion against cycles

---------

Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 4985671)

* fix(opencode-go): re-arm idle timer on block-boundary events to prevent false stalled-stream abort (#97128)

* fix(opencode-go): re-arm idle timer on block-boundary events to prevent false stalled-stream abort

When the opencode-go model finalizes a tool call and deliberates before
the next one, the provider emits real block-boundary SSE events
(text_end, thinking_end, toolcall_start, toolcall_end) that prove the
socket is alive, but the watchdog's isProviderProgressEvent only
returned true for token deltas (text_delta, thinking_delta,
toolcall_delta). This caused the idle timer to fire and falsely abort a
live stream, replacing a completed answer with a stalled error and
dropping the provider's real done event.

Fix: include block-boundary events in isProviderProgressEvent so the
idle timer is re-armed on any forward-progress provider event.
text_start and thinking_start are intentionally excluded because they
are synthetic preamble events that should not shorten the first-event
window.

Closes #96518

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* test(opencode-go): satisfy lint in stream regression

* test(opencode-go): satisfy lint in stream regression

* test(opencode-go): satisfy lint in stream regression

---------

Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 552ec2b)

* fix(model-fallback): don't rethrow provider-side AbortErrors as user cancellations (#90908)

* fix(model-fallback): don't rethrow provider-side AbortErrors as user cancellations

When the LLM API closes the connection mid-stream, the fetch layer
surfaces AbortError("This operation was aborted") with no external
abort signal triggered. The old guard `shouldRethrowAbort()` returned
false for these errors (because isTimeoutError matched the message),
so they fell through to the fallback loop but were never retried —
the error propagated up and produced SILENT_REPLY_TOKEN in group
sessions, permanently silencing the topic.

Replace the guard with a direct check: only rethrow AbortError when
the external abort signal is actually set (user/gateway cancellation).
Provider-side AbortErrors without an external signal now fall through
to the next fallback candidate, giving the system a chance to recover.

* fix(cron): forward abort signal into runWithModelFallback

Thread the cron executor's abort signal into the shared
runWithModelFallback call so that cron timeouts and cancellations
stop the fallback chain instead of retrying with the next candidate.

Previously, the run callback checked params.abortSignal?.aborted and
threw, but runWithModelFallback itself had no signal — so the new
guard in model-fallback.ts could not distinguish a caller abort from
a provider-side AbortError and would retry silently.

Also adds a focused regression test verifying the signal is forwarded.

---------

Co-authored-by: Shengting Xie <[email protected]>
Co-authored-by: yayu <[email protected]>
(cherry picked from commit 98ed83f)

* fix(browser): block node routes when sandbox host control is disabled (#97958)

(cherry picked from commit 2cf765f)

* fix(exec): bind Windows allowlist execution path (#98260)

* fix(exec): bind windows allowlist execution path

* fix(exec): add windows shadow execution proof

* fix(exec): preserve wildcard allowlist behavior

* fix(exec): correct blocked plan test fixture

(cherry picked from commit 3811001)

* fix(mcp): suppress unhandled error on stderr pipe in stdio transport (#99803)

* fix(mcp): suppress unhandled error on stderr pipe in stdio transport

When child.stderr is piped to stderrStream without an error
handler, a stream-level error (EPIPE, I/O failure) crashes the
process. Add a noop error handler before the pipe, consistent
with the error handlers already present on stdin and stdout.

Co-Authored-By: Claude <[email protected]>

* test(mcp): add regression test for stderr pipe error suppression

Co-Authored-By: Claude <[email protected]>

* fix(mcp): report stderr stream errors

* fix(mcp): report stderr stream errors

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 1b84316)

* Harden macOS SQLite WAL checkpoints (#99067)

(cherry picked from commit f7f1be2)

* fix(secrets): suppress unhandled stdout/stderr stream errors in exec resolver (#100521)

* fix(secrets): suppress unhandled stdout/stderr stream errors in exec resolver

* proof(secrets): add real behavior proof script for exec resolver stream error catch

* proof(secrets): replace wrapper with real exec resolver stream error proof

* style: apply oxfmt to changed files

(cherry picked from commit c9a0783)

* fix(agents): retry transient filesystem races when reading workspace bootstrap files (#100910)

* fix(agents): retry transient filesystem races when reading workspace bootstrap files

* fix(agents): retry transient boundary resolution

---------

Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit f36d170)

* fix(gateway): finish plugin HTTP responses after post-header failures (#102125)

* fix(gateway): finish plugin HTTP responses after post-header failures

* test(gateway): satisfy plugin HTTP regression lint

* fix(gateway): skip ending destroyed plugin responses

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 240d350)

* fix(gateway): validate exact custom browser origins (#38290)

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit fa0349a)

* fix: block unspecified trusted DNS targets (#103075)

(cherry picked from commit c70f3d0)

* fix(channels): make nack callbacks idempotent (#104919)

* fix(channels): make nack callbacks idempotent

* fix(channels): coalesce overlapping nack callbacks

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 02d307e)

* fix(channels): prevent base URL credentials in status output (#107754)

* fix(channels): redact credentials in account URLs

* fix(channels): sanitize final status summaries

(cherry picked from commit 210340f)

* fix(channels): prevent lifecycle listener buildup (#109108)

(cherry picked from commit 0e1fad7)

* fix(sandbox): use Buffer.byteLength for env var value size limit (#105017)

* fix(sandbox): use Buffer.byteLength for env var value size limit

validateEnvVarValue checked value.length (UTF-16 code units) against
the 32768-byte limit, so multi-byte CJK values like "值".repeat(11000)
passed the check despite exceeding 33 KB in UTF-8. Switch to
Buffer.byteLength(value, "utf8") so the limit matches the actual byte
count the OS and child processes see.

* test(sandbox): simplify env byte-limit coverage

Co-authored-by: 唐梓夷0668001293 <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 84fb48c)

* fix(gateway): guard process.kill ESRCH race in signalVerifiedGatewayPidSync (#109590)

* fix(gateway): guard process.kill ESRCH race in signalVerifiedGatewayPidSync

A verified gateway process can exit between the argv validation check and
the process.kill call, causing an unhandled ESRCH error. Wrap the kill in
try-catch and silently swallow ESRCH (process already gone = signal
already delivered).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* docs(gateway): explain ESRCH signal race

Co-authored-by: 丁宇婷0668001435 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 853b1a8)

* fix(litellm): guard loopback hostname auto-allow with isIP to prevent DNS SSRF bypass (#110693)

* fix(litellm): guard loopback hostname auto-allow with isIP to prevent DNS bypass

The isAutoAllowedLitellmHostname helper auto-enables private-network access
for loopback-style hosts. Before this fix, lowered.startsWith("127.")
matched DNS hostnames like 127.evil.com, letting remote endpoints bypass
the explicit allowPrivateNetwork opt-in — a SSRF risk.

Add isIP(host)===4 guard so only literal IPv4 loopback addresses qualify.
Same canonical pattern as extensions/slack/src/monitor/relay-source.ts:271
and the codex loopback fix.

Co-Authored-By: Claude <[email protected]>

* test(litellm): cover loopback endpoint policy

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 3d03b60)

* fix(discord): sustained gateway bursts stop growing memory (#110954)

* fix(discord): sustained gateway bursts stop growing memory

* fix(discord): contain gateway queue overflow

* fix(discord): drop oldest saturated gateway sends

Co-authored-by: 张贵萍0668001030 <[email protected]>

* fix(discord): surface gateway overflow warnings

Co-authored-by: 张贵萍0668001030 <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 69aeba9)

* fix(gateway): bound busy channel health by real run age (#103793)

* fix(gateway): bound busy channel health by real run age

The channel health policy treats a channel as healthy-busy even while
disconnected, bounded only by a 25 minute stale ceiling measured from
lastRunActivityAt. The run-state heartbeat refreshes lastRunActivityAt
every 60 seconds for as long as any run is active, so a run that hangs
forever (for example a send blocking on a dead socket after the
transport already reported connected:false) keeps that timestamp fresh
and the stuck ceiling is never reached. The account is then reported
healthy forever by the health monitor, readiness probe, and health CLI,
and no restart ever fires.

createRunStateMachine now tracks each in-flight run's start time keyed by
an opaque run handle and publishes the oldest still-active run's start as
activeRunStartedAt. The health policy busy override keys its ceiling off
the real run age, so a run stuck longer than the threshold reports stuck
and the monitor can restart it. Because the reported start is the oldest
active run and advances to the next-oldest as runs complete, a channel
churning through many short overlapping runs (activeRuns above 1 across
concurrent queue keys) stays healthy; only a genuinely hung run breaches
the ceiling. Short and active runs stay healthy and the existing
lastRunActivityAt fallback is preserved for snapshots without a start
time.

* fix(channels): retain run-state callback compatibility

Keep the released zero-argument onRunEnd callback source-compatible while allowing internal queue callers to pass a run handle for exact concurrent-run accounting. The compatibility path closes the oldest active run, preserving existing lifecycle behavior for consumers that do not use handles.

* fix(channels): keep anonymous runs out of age tracking

The zero-argument lifecycle callbacks cannot identify which concurrent run completed, so they must not update the identity-sensitive run start used by channel health. Keep their busy count separately and reserve exact start tracking for the shared queue's handle-aware lifecycle path.

* fix(channels): keep tracked runs internal

Keep the public run-state lifecycle callbacks unchanged. The channel queue now owns opaque run identity and augments its status updates with the oldest active queue run, so implementation details do not expand the SDK surface.

* fix(channels): type queue run start status

Keep activeRunStartedAt in the internal status patch type so the queue can publish its private tracked-run age through the existing status sink.

* fix(channels): wrap isActive to satisfy unbound-method lint

* fix(gateway): gate busy run-age ceiling on disconnected transport

(cherry picked from commit 18b79d9)

* fix(deps): update fast-uri past advisory

(cherry picked from commit 1be9db0)

* fix(release): adapt maintenance-line hardening

Backport/adapt 18ec9ce, dea1fe1, 7f32b6c, 1da345e, 931ac3e, 89780d5, and c0d99ed for the 2026.6 extended-stable maintenance line.

* fix(deps): bump protobufjs to 7.6.5

Backport-adapted from a230f74.

* test(gateway): cover bounded macOS process probe

* chore(release): prepare 2026.6.34

* test(dotenv): share path override environment assertions

* fix(release): resolve 2026.6.34 CI blockers

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: joshavant <[email protected]>
Co-authored-by: Peter Lee <[email protected]>
Co-authored-by: sallyom <[email protected]>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: Liu Wenyu <[email protected]>
Co-authored-by: pick-cat <[email protected]>
Co-authored-by: Pick-cat <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Co-authored-by: weiqinl <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: shengting <[email protected]>
Co-authored-by: Shengting Xie <[email protected]>
Co-authored-by: yayu <[email protected]>
Co-authored-by: Agustin Rivera <[email protected]>
Co-authored-by: cxbAsDev <[email protected]>
Co-authored-by: ooiuuii <[email protected]>
Co-authored-by: Masato Hoshino <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Co-authored-by: mushuiyu886 <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
Co-authored-by: Bruno Wowk (Volky) <[email protected]>
Co-authored-by: Pavan Kumar Gondhi <[email protected]>
Co-authored-by: Glucksberg <[email protected]>
Co-authored-by: xingzhou <[email protected]>
Co-authored-by: tzy-17 <[email protected]>
Co-authored-by: krissding <[email protected]>
Co-authored-by: lsr911 <[email protected]>
Co-authored-by: Yuval Dinodia <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants