Skip to content

fix(reef): contain startup friend reconcile failures#110918

Merged
steipete merged 9 commits into
openclaw:mainfrom
Yigtwxx:fix/reef-startup-reconcile-contained
Jul 18, 2026
Merged

fix(reef): contain startup friend reconcile failures#110918
steipete merged 9 commits into
openclaw:mainfrom
Yigtwxx:fix/reef-startup-reconcile-contained

Conversation

@Yigtwxx

@Yigtwxx Yigtwxx commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Related: #110870

What Problem This Solves

Fixes an issue where operators running the Reef channel against a relay that is rate limiting would see Reef stop permanently and stay down until the process was restarted by hand, instead of riding out the throttling and recovering on its own.

While the relay returns 429, starting the Reef account fails outright. The gateway supervisor restarts it, each restart hits the same throttled relay again, and after ten attempts Reef gives up for the lifetime of the process — logging giving up after 10 restart attempts. Because every attempt issues another relay request, the restart cycle also feeds the throttling that caused it.

#110870 fixed exactly this amplifier for the periodic reconcile that runs every 30s, but the reconcile that runs once at account startup was not covered by that change and still fails the whole account.

Why This Change Was Made

The startup and periodic reconcile are the same relay call with two different failure policies: the periodic one reports the error and keeps the channel alive, the startup one rejects startAccount. This change gives them one shared policy, in the module that already owns the account lifecycle.

The startup reconcile moves into runReefChannelLifecycle, and activation moves behind a new onReady hook so the original ordering is preserved exactly — refresh peer keys, activate, then start the inbox — since an agent turn must not use Reef outbound before peer keys are refreshed. Both call sites now route through one internal runReconcileStep helper.

Deliberate boundary: on a startup failure the account continues with the peer keys already persisted, which is the same staleness the periodic path already tolerates between intervals, and the next interval retries. Two adjacent fixes fall out of the restructure — the abort listener on parentSignal is no longer leaked if a startup step throws, and an already-aborted account no longer issues a relay request on its way out.

User Impact

Reef survives relay rate limiting instead of dying to it. When the relay throttles at startup, the account now comes up, logs reef friend reconcile failed: ..., serves inbound and outbound messages with the peer keys it already has, and refreshes them on the next 30s reconcile once the relay recovers. Operators no longer need to restart the process to get Reef back, and Reef stops adding restart traffic to a relay that is already throttling.

No configuration change. The only behavior difference on the success path is that peer keys may briefly be one interval stale after a failed startup refresh.

Evidence

Live run against a local relay returning 429 {"error":"rate_limited"} on GET /v1/friends, driving the real ReefTransportClient — real request signing, real ReefRelayError — through both startup sequences:

local relay on http://127.0.0.1:59686 (429 on every request)

main (bare `await reconcile()` at channel.ts:405):
  [relay] GET /v1/friends -> 429
  startAccount: THREW -> ReefRelayError: rate_limited
  activated=false inboxStarted=false

patched (startup reconcile inside runReefChannelLifecycle):
  [relay] GET /v1/friends -> 429
  startAccount: still running (no throw)
  activated=true inboxStarted=true
  reported: ReefRelayError: rate_limited
  clean shutdown after abort

Harness scope: it drives the real transport, signing and lifecycle, and substitutes the trust-store bookkeeping that wraps listFriends inside friends.reconcile(). That bookkeeping is not what throws, and this machine cannot open the plugin state DB (see the note below).

Tests — two added to extensions/reef/src/channel.test.ts:

  • activates and starts the inbox when the startup reconcile fails captures the reconcile and error counts inside onReady, so it pins the startup reconcile specifically rather than "some reconcile eventually failed" after the periodic loop has had a chance to run.
  • refreshes peer keys before activating and before the inbox starts pins the reconcile → ready → inbox ordering.

Both are load-bearing — restoring a bare await params.reconcile() at startup fails them:

× activates and starts the inbox when the startup reconcile fails
× keeps running when a periodic reconcile fails
  Tests  3 failed | 5 passed (8)

tears down the inbox loop before settling when a loop branch throws needed one adjustment. It drives its failure through onReconcileError throwing, which now also fires on the startup step, so the inbox would never start and there would be nothing to tear down. Its startup reconcile now succeeds and the failure lands on the periodic loop with the inbox running, which is the case that test was written to cover.

Local checks: oxlint and oxfmt --check clean on the three files, tsgo --noEmit clean, extensions/reef/src/channel.test.ts 7 passed / 1 failed.

That one failure is exposes locally trusted peers as routable directory entries, which fails identically on unmodified origin/main here (1 failed / 5 passed): this machine runs Node 22.20.0, whose embedded SQLite 3.50.4 is below the WAL-safety floor the plugin state store requires. Unrelated to this change. The max-lines ratchet likewise reports ui/src/components/lobster-pet.ts on this base with and without this change; not touched here.

AI-assisted.

@openclaw-barnacle openclaw-barnacle Bot added channel: reef Channel integration: reef size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. and removed triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jul 18, 2026
@Yigtwxx

Yigtwxx commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Rewrote the description against the PR template after the initial needs-pr-context triage — problem, rationale, user impact and evidence are now under the template's headings.

@clawsweeper

clawsweeper Bot commented Jul 18, 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: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 High-priority user-facing bug, regression, or broken workflow. labels Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 18, 2026, 6:10 PM ET / 22:10 UTC.

Summary
The PR routes Reef startup friend reconciliation through the shared lifecycle, continues after classified retryable relay failures, and adds lifecycle and transport regression coverage.

PR surface: Source +95, Tests +232, Docs +1. Total +328 across 6 files.

Reproducibility: yes. source inspection and the submitted live relay-429 transcript provide a high-confidence path: make the startup friends request return 429 and compare account activation before and after the shared lifecycle path.

Review metrics: 1 noteworthy metric.

  • Startup reconcile policy: 1 startup path changed from fail-start to classified continuation. This single lifecycle-policy change determines whether a rate-limited account remains available with persisted peer keys or is restarted by the supervisor.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

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

Rank-up moves:

  • [P2] Obtain explicit Reef-owner confirmation that temporary persisted peer-key staleness is acceptable for the classified retryable failures before merge.

Risk before merge

  • [P1] Merging intentionally keeps a Reef account available when startup peer-key reconciliation fails transiently, so outbound or inbound work can briefly use persisted peer keys until the next 30-second reconcile succeeds.
  • [P1] The retryable classification changes an account-start failure into continued operation for relay 408, 429, 5xx, normalized transport loss, and timeouts; Reef ownership should explicitly accept that availability-versus-freshness policy before merge.

Maintainer options:

  1. Confirm the persisted-key availability policy (recommended)
    Approve the retryable-startup fallback only if Reef owners accept temporary peer-key staleness as preferable to an account outage during relay throttling.
  2. Narrow or remove the fallback
    Keep startup fail-closed, or reduce continuation to an explicitly approved failure subset, if fresh peer keys are required before account traffic.

Next step before merge

  • [P2] No narrow automated repair is indicated; the remaining merge gate is an explicit Reef-owner decision on the availability-versus-stale-peer-key policy.

Maintainer decision needed

  • Question: Should Reef continue startup with persisted peer keys after a retryable relay reconciliation failure, rather than fail the account and rely on supervisor restart?
  • Rationale: The implementation is mechanically narrow and tested, but it deliberately changes the product safety boundary from fail-start to temporary key staleness for several transient relay and transport failures.
  • Likely owner: steipete — They authored the merged Reef recovery work, authored the validation follow-ups in this branch history, and were assigned to this PR.
  • Options:
    • Confirm persisted-key fallback (recommended): Accept continued availability during retryable startup failures, relying on the next reconciliation interval to refresh peer keys.
    • Keep startup fail-closed: Require a successful startup reconciliation and reject the PR's continuation path if fresh peer keys are mandatory before any account activity.
    • Narrow the retryable set: Permit only specifically approved transient failures, such as relay throttling, and retain fail-start behavior for the remaining retryable transport classes.

Security
Cleared: The diff changes Reef lifecycle and transport error handling without adding dependencies, privileged execution, secrets access, workflow changes, or supply-chain inputs.

Review details

Best possible solution:

Land the shared lifecycle path only after the Reef owner confirms that using persisted peer keys during a retryable startup refresh failure is the intended availability policy, with definitive failures still stopping startup.

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

Yes, source inspection and the submitted live relay-429 transcript provide a high-confidence path: make the startup friends request return 429 and compare account activation before and after the shared lifecycle path.

Is this the best way to solve the issue?

Unclear until a Reef owner confirms the intended stale-peer-key policy; the shared lifecycle is the narrowest implementation if that policy is accepted, but it is not solely a mechanical bug fix.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P1: Relay throttling can leave a Reef account unavailable and disrupt active channel traffic until manual recovery.
  • merge-risk: 🚨 message-delivery: The fallback can temporarily use persisted peer-key state while a failed startup refresh waits for the next reconcile interval.
  • merge-risk: 🚨 availability: The patch deliberately changes retryable startup failures from account termination to continued operation, making the startup failure policy merge-critical.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes an after-fix live run against a local relay returning 429 through the real Reef transport, showing activation and inbox startup after the handled failure; no additional contributor proof is required.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes an after-fix live run against a local relay returning 429 through the real Reef transport, showing activation and inbox startup after the handled failure; no additional contributor proof is required.
Evidence reviewed

PR surface:

Source +95, Tests +232, Docs +1. Total +328 across 6 files.

View PR surface stats
Area Files Added Removed Net
Source 3 120 25 +95
Tests 2 236 4 +232
Docs 1 1 0 +1
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 6 357 29 +328

What I checked:

  • Startup failure policy: The proposed lifecycle adds a startup-specific continuation classifier and invokes activation only after the startup reconciliation either succeeds or reports a classified retryable failure; definitive failures still propagate. (extensions/reef/src/channel-lifecycle.ts:32, 0f916a42f51e)
  • Account activation ordering: The Reef channel moves activation behind the lifecycle ready hook, retaining the intended reconcile-attempt → activation → inbox sequence while avoiding a startup rejection for retryable relay failures. (extensions/reef/src/channel.ts:406, 0f916a42f51e)
  • Retryability boundary: The transport change classifies relay 408, 429, and 5xx responses plus normalized transport unavailability and timeouts as retryable, while tests retain definitive relay and local failures as non-retryable. (extensions/reef/src/transport.ts:53, 0f916a42f51e)
  • Regression coverage: The added lifecycle tests distinguish a failed startup reconcile from a later periodic reconcile and pin reconciliation before ready/inbox ordering. (extensions/reef/src/channel.test.ts:136, 0f916a42f51e)
  • Related merged recovery work: The merged Reef recovery PR fix(reef): stop leaking the inbox loop across channel crash restarts #110870 already contained the shared lifecycle and periodic-reconcile containment; this PR addresses the remaining startup call site rather than duplicating that landed fix. (extensions/reef/src/channel-lifecycle.ts:1, e80b0b5aa503)
  • Real behavior evidence: The PR body records an after-fix run against a local relay returning HTTP 429 through the real Reef transport: the main behavior throws before activation, while the patch reports the error and reaches activation and inbox startup. (0f916a42f51e)

Likely related people:

  • steipete: Authored the merged Reef reconcile-recovery work and the follow-up validation commits now present in this PR's history; the current policy decision is adjacent to that lifecycle boundary. (role: recent Reef recovery contributor and merger; confidence: high; commits: b7c564bd568c, daac74a03cc3, e625e7363fc9; files: extensions/reef/src/channel-lifecycle.ts, extensions/reef/src/channel.ts)
  • Yigtwxx: Co-authored the earlier merged Reef reconcile hardening and proposed the focused startup continuation path, giving them direct context on the lifecycle regression. (role: prior Reef recovery co-author; confidence: medium; commits: b7c564bd568c, 8b8235f97322; files: extensions/reef/src/channel-lifecycle.ts, extensions/reef/src/transport.ts, extensions/reef/src/channel.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 (6 earlier review cycles)
  • reviewed 2026-07-18T19:29:03.212Z sha e5f83f8 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-18T19:35:42.303Z sha 8b8235f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-18T20:20:19.797Z sha 2046dfc :: needs maintainer review before merge. :: none
  • reviewed 2026-07-18T20:32:35.332Z sha 2046dfc :: needs maintainer review before merge. :: none
  • reviewed 2026-07-18T21:45:43.373Z sha daac74a :: needs maintainer review before merge. :: none
  • reviewed 2026-07-18T22:00:06.746Z sha e625e73 :: needs maintainer review before merge. :: none

The periodic reconcile treats a transient relay failure as non-fatal, but
the startup reconcile was a bare await outside the lifecycle. A relay 429
there rejects startAccount, and the supervisor restart that follows is
itself what escalates relay rate limiting -- so Reef gives up permanently
after MAX_RESTARTS while the relay is still throttling.

Move the startup reconcile into runReefChannelLifecycle so both paths
share one failure policy, and activate through an onReady hook to preserve
the existing ordering: refresh peer keys, activate, then start the inbox.
@Yigtwxx
Yigtwxx force-pushed the fix/reef-startup-reconcile-contained branch from e5f83f8 to 8b8235f Compare July 18, 2026 19:32
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jul 18, 2026
@Yigtwxx

Yigtwxx commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

CI note: check-sqlite-session-flip-proof is red here but the failure is a flake, not this diff.

I force-pushed once to refresh the checks (the earlier Real behavior proof failure was from the pre-template description, since fixed). That push kept the tree byte-identical — both commits point at tree 7b631c4c00ae08c8a3470c6df253a1adf02457cb — so the two runs below differ only in when they ran:

  • e5f83f8check-sqlite-session-flip-proof success, 19:24:31Z
  • 8b8235fcheck-sqlite-session-flip-proof failure, 19:33:29Z

The assertion that fails is sessions.cleanup did not prune stale SQLite rows (beforeCount: 36, afterCount: 36). This PR only touches extensions/reef/, and does not go near the session store, cleanup, or SQLite. openclaw/ci-gate is red as a consequence of that shard.

I have not pushed again to re-roll it, since that only adds churn and risks a different flake. Happy to rebase if you would rather see a clean run before merging.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. and removed rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. labels Jul 18, 2026
@steipete steipete self-assigned this Jul 18, 2026
@steipete
steipete merged commit ee37c84 into openclaw:main Jul 18, 2026
105 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Merged via squash.

@Yigtwxx
Yigtwxx deleted the fix/reef-startup-reconcile-contained branch July 18, 2026 22:21
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 19, 2026
* fix(reef): contain startup friend reconcile failures

The periodic reconcile treats a transient relay failure as non-fatal, but
the startup reconcile was a bare await outside the lifecycle. A relay 429
there rejects startAccount, and the supervisor restart that follows is
itself what escalates relay rate limiting -- so Reef gives up permanently
after MAX_RESTARTS while the relay is still throttling.

Move the startup reconcile into runReefChannelLifecycle so both paths
share one failure policy, and activate through an onReady hook to preserve
the existing ordering: refresh peer keys, activate, then start the inbox.

* fix(reef): harden reconcile recovery

Co-authored-by: Yigtwxx <[email protected]>

* docs(changelog): credit Reef reconcile fix

* test(reef): satisfy exact-head validation

* test(reef): satisfy lint contract

---------

Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: reef Channel integration: reef merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants