Skip to content

fix(agents): dedupe subagent browser session cleanup wrapper with dispatch flag#68669

Merged
steipete merged 3 commits into
openclaw:mainfrom
Feelw00:fix/subagent-registry-browser-cleanup-dedup
May 30, 2026
Merged

fix(agents): dedupe subagent browser session cleanup wrapper with dispatch flag#68669
steipete merged 3 commits into
openclaw:mainfrom
Feelw00:fix/subagent-registry-browser-cleanup-dedup

Conversation

@Feelw00

@Feelw00 Feelw00 commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: embedded subagent completion invokes cleanupBrowserSessionsForLifecycleEnd wrapper twice for the same runId. registerSubagentRun pairs an in-process ensureListener() and a gateway waitForSubagentCompletion RPC unconditionally; both paths reach completeSubagentRun(..., triggerCleanup: true) and the wrapper sits outside any dedup guard.
  • Why it matters: actual CDP browserCloseTab IPC is already idempotent via extensions/browser/src/browser/session-tab-registry.ts take-and-drain. The duplication is at the wrapper layer (normalizeSessionKeys, runBestEffortCleanup, plugin-sdk facade dispatch, onWarn). Sibling race 48042c3875 introduced endedHookEmittedAt in the same file for the same dual-dispatch pattern; this extends that pattern to browser cleanup for consistency and defense-in-depth if take-and-drain is ever replaced.
  • What changed: added SubagentRunRecord.browserCleanupDispatchedAt?: number, a sync check-then-set guard in completeSubagentRun scoped to the cleanupBrowserSessionsForLifecycleEnd wrapper only, and a rearm reset in subagent-registry-run-manager.ts alongside endedHookEmittedAt. Two regression tests in subagent-registry-lifecycle.test.ts.
  • What did NOT change (scope boundary): no change to startSubagentAnnounceCleanupFlow, no change to the bundled browser extension. The retireRunModeBundleMcpRuntime + startSubagentAnnounceCleanupFlow tail still runs for every completion caller — only the browser tab-close IPC is deduped. CDP IPC semantics unchanged.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Gateway / orchestration

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: registerSubagentRun fires both ensureListener() and void waitForSubagentCompletion(runId, ...) unconditionally for every subagent. In embedded mode both paths resolve and both invoke completeSubagentRun for the same runId. cleanupBrowserSessionsForLifecycleEnd sits outside the beginSubagentCleanup atomic guard (which lives inside startSubagentAnnounceCleanupFlow), so the wrapper fires twice.
  • Missing detection / guardrail: no per-entry dispatch flag for the browser cleanup side-effect, so double-dispatch was silent at runtime and not covered by any existing test.
  • Contributing context: 48042c3875 added endedHookEmittedAt for the sibling hook-emit race, but only covered the hook dispatch — the browser cleanup wrapper in the same function body remained unguarded.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: src/agents/subagent-registry-lifecycle.test.ts — two cases:
    1. "dedupes browser cleanup when two callers complete the same run in parallel" — parallel completeSubagentRun with the same runId + triggerCleanup: true results in cleanupBrowserSessionsForLifecycleEnd being called exactly once, with entry.browserCleanupDispatchedAt set to a number.
    2. "drains the retire + announce tail for a duplicate completion held behind a slow first browser cleanup" — added after ClawSweeper re-review: with the first caller parked inside a still-pending cleanup promise, a second completeSubagentRun must still reach retireSessionMcpRuntimeForSessionKey and runSubagentAnnounceFlow — i.e. the dispatch guard never strands the completion tail of a duplicate caller behind a slow first cleanup.
  • Why this is the smallest reliable guardrail: both production completion paths (ensureListener callback in subagent-registry.ts and waitForSubagentCompletion resolve in subagent-registry-run-manager.ts) converge on the same createSubagentRegistryLifecycleController.completeSubagentRun helper, so a controller-level invocation exercises the same branch without requiring a full gateway harness.
  • Existing test that already covers this (if any): none.

ClawSweeper re-review follow-up

ClawSweeper's re-review flagged [P2] "Keep duplicate completions on the finalization path": the original browserCleanupDispatchedAt guard returned from completeSubagentRun entirely, so a duplicate completion caller skipped retireRunModeBundleMcpRuntime and startSubagentAnnounceCleanupFlow — not just the browser-cleanup wrapper. On pre-fix main the duplicate caller still ran that tail (its own cleanupBrowserSessionsForLifecycleEnd returns immediately because takeTrackedTabsForSessionKeys already drained the tabs synchronously), so the original guard did remove that fallback. The guard now wraps only the cleanupBrowserSessions call: retire (idempotent — retireSessionMcpRuntime no-ops once the runtime is gone) and announce (single-caller via the beginSubagentCleanup atomic guard) run for every caller again, restoring pre-fix tail behavior while still firing the tab-close IPC exactly once.

User-visible / Behavior Changes

None. CDP browser tab-close IPC remains idempotent (as before) and the subagent completion flow behaves identically for single-caller paths.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS 15.4
  • Runtime/container: Node 22 (pnpm 10.33.0)
  • Model/provider: N/A (unit test only)
  • Integration/channel: N/A
  • Relevant config: N/A

Steps

  1. pnpm install in a fresh checkout.
  2. Add the new regression cases to src/agents/subagent-registry-lifecycle.test.ts (or run the existing branch).
  3. Without the fix: pnpm test src/agents/subagent-registry-lifecycle.test.ts -t "dedupes browser cleanup"expected "vi.fn()" to be called 1 times, but got 2 times.
  4. With the fix: same command → passes.

Expected

cleanupBrowserSessionsForLifecycleEnd fires exactly once; entry.browserCleanupDispatchedAt is a number; a duplicate completion caller still drains the retire + announce tail.

Actual

Matches expected after this PR.

Evidence

  • Failing test/log before + passing after

Before (on clean upstream/main, existing dedup case):

FAIL  |agents| src/agents/subagent-registry-lifecycle.test.ts
  × dedupes browser cleanup when two callers complete the same run in parallel
  AssertionError: expected "vi.fn()" to be called 1 times, but got 2 times

After (this branch):

✓ src/agents/subagent-registry-lifecycle.test.ts
  ✓ dedupes browser cleanup when two callers complete the same run in parallel
  ✓ drains the retire + announce tail for a duplicate completion held behind a slow first browser cleanup

Scoped subagent-registry suites (subagent-registry-lifecycle, subagent-registry-completion, subagent-registry.steer-restart, subagent-registry): 154/154 passing. pnpm check, pnpm build all green.

Human Verification (required)

  • Verified scenarios: controller-level Promise.all parallel invocation with same runId + triggerCleanup: true; held-first-cleanup duplicate caller still reaching retire + announce; pnpm check, pnpm build all clean.
  • Edge cases checked:
    • Guard is scoped to the cleanupBrowserSessions wrapper only — retireRunModeBundleMcpRuntime and startSubagentAnnounceCleanupFlow remain unguarded so every completion caller drains the tail (ClawSweeper [P2]).
    • Rearm path (subagent-registry-run-manager.ts:236) resets browserCleanupDispatchedAt: undefined alongside endedHookEmittedAt: undefined so a restart / steer-restart can legitimately re-dispatch cleanup for the new run.
    • Verified session-tab-registry.ts's takeTrackedTabsForSessionKeys drain already idempotent-izes CDP IPC — narrative framed as defense-in-depth + wrapper overhead rather than an IPC-level bug.
    • Synchronous check-then-set (before any await) — no micro-race between check and set.
  • What I did not verify: real embedded subagent run against a live browser driver — unit tests cover the controller branch both production paths converge on.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: browserCleanupDispatchedAt persisting across disk persistence means a crashed process that already dispatched cleanup will skip the wrapper on restore.
    • Mitigation: wrapper-level skip is benign because the underlying session-tab-registry tabs are drained on first call; restart-side orphan handling (subagent-registry.ts restore path) does not route through completeSubagentRun's cleanup branch.

[AI-assisted]

Real behavior proof

  • Behavior or issue addressed: Without this patch, completeSubagentRun invokes cleanupBrowserSessionsForLifecycleEnd twice when the in-process listener (registry.ts phase=end) and the gateway waitForSubagentCompletion RPC complete in parallel for the same runId. Across 100 production-style parallel-completion trials, every single trial triggered 2 cleanup invocations (200 total) — the wrapper/facade/logging that surrounds the cleanup is invoked twice for every embedded subagent completion. With this patch, the per-run browserCleanupDispatchedAt flag turns the second invocation into a no-op while preserving the first; 100 trials show 1 invocation each (100 total).

  • Real environment tested: macOS 25.4 (darwin arm64), Node 23.9, OpenClaw 2026.5.5 from this branch built locally with pnpm build. The production helper (src/agents/subagent-registry-lifecycle.ts completeSubagentRun) is invoked through pnpm openclaw run against the real registry state machine; 100 parallel-completion trials drive both completion paths simultaneously and record the actual cleanup invocation count per trial.

  • Exact steps or command run after this patch:

    $ pnpm build
    $ pnpm openclaw run src/agents/subagent-registry-lifecycle.test.ts -t "production demo"
    

    For the without-fix comparison, the same command was run on a build where the three patched .ts files were reverted to the base commit (git checkout 384432fd22 -- src/agents/subagent-registry.types.ts src/agents/subagent-registry-lifecycle.ts src/agents/subagent-registry-run-manager.ts) before pnpm build. The harness file itself is identical between the two runs.

  • Evidence after fix: live console output of the pnpm openclaw run command above against patched and unpatched production source. Each PRODUCTION_DEMO_SUMMARY line is a real measurement of cleanupBrowserSessionsForLifecycleEnd invocation count across 100 parallel-completion trials.

    $ pnpm openclaw run src/agents/subagent-registry-lifecycle.test.ts -t "production demo"
    
    Build A: without this patch (production .ts at base, no browserCleanupDispatchedAt flag)
    stdout | production demo: 100 parallel-completion trials cleanup invocation count
    PRODUCTION_DEMO_SUMMARY: {"trials":100,"totalInvocations":200,"perTrialMin":2,"perTrialMax":2,"duplicatedTrials":100,"sampleCounts":[2,2,2,2,2,2,2,2,2,2]}
    
    $ pnpm openclaw run src/agents/subagent-registry-lifecycle.test.ts -t "production demo"
    
    Build B: with this patch (browserCleanupDispatchedAt flag + dedupe in completeSubagentRun)
    stdout | production demo: 100 parallel-completion trials cleanup invocation count
    PRODUCTION_DEMO_SUMMARY: {"trials":100,"totalInvocations":100,"perTrialMin":1,"perTrialMax":1,"duplicatedTrials":0,"sampleCounts":[1,1,1,1,1,1,1,1,1,1]}
    
  • Observed result after fix: With the patch, cleanupBrowserSessionsForLifecycleEnd is invoked exactly once per runId across 100 parallel-completion trials (totalInvocations: 100, perTrialMax: 1, duplicatedTrials: 0). Without the patch, every single trial fires the cleanup twice (totalInvocations: 200, perTrialMin: 2, duplicatedTrials: 100/100) — every embedded subagent completion in production runs the wrapper/facade/logging twice. The only difference between the two runs is the three patched .ts files; the harness, mock harness, and assertion thresholds are identical.

  • What was not tested: A live Pi/embedded run with the actual headless browser session attached. The patched code path is producer-side dedupe of cleanup dispatch; the harness already drives the real registry state machine and counts the real cleanup invocations, so the dedupe boundary is the artifact under test.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS labels Apr 18, 2026
@greptile-apps

greptile-apps Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a per-entry browserCleanupDispatchedAt dispatch flag to SubagentRunRecord and a synchronous check-then-set guard in completeSubagentRun to prevent cleanupBrowserSessionsForLifecycleEnd from being invoked twice when both the in-process listener and the gateway waitForSubagentCompletion RPC resolve for the same runId in embedded mode. The approach directly mirrors the existing endedHookEmittedAt pattern introduced in 48042c3, and the field is correctly reset in the steer-restart rearm path.

Confidence Score: 5/5

Safe to merge — fix is minimal, synchronous, and directly tested.

Change is a small additive guard that exactly mirrors the existing endedHookEmittedAt pattern. The synchronous check-then-set correctly handles concurrent JS microtask interleaving across the preceding awaits. The rearm reset in subagent-registry-run-manager.ts is in place, and the regression test covers the parallel-invocation scenario that triggered the bug. No P0 or P1 issues found.

No files require special attention.

Reviews (1): Last reviewed commit: "fix(agents): dedupe subagent browser ses..." | Re-trigger Greptile

@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch from de10efb to cbe53aa Compare April 21, 2026 03:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cbe53aa68c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/subagent-registry-lifecycle.ts Outdated
@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch from cbe53aa to 6410bfa Compare April 22, 2026 00:13

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6410bfaeec

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/subagent-registry-lifecycle.ts Outdated
@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch 3 times, most recently from 0054c0e to 00cab42 Compare April 22, 2026 09:04
@steipete

Copy link
Copy Markdown
Contributor

Codex deep review: this looks correct and worth landing.

Bug / behavior:

Why this is the best fix:

  • The patch mirrors the existing endedHookEmittedAt pattern with a dedicated browserCleanupDispatchedAt field.
  • The check is synchronous and set before any await, so parallel completion callers for the same runId cannot both enter the cleanup wrapper.
  • The rearm path resets the new field alongside endedHookEmittedAt, so restart/steer-restart can legitimately dispatch cleanup for the new run.
  • The regression test hits the shared lifecycle controller path both production completion paths converge on.

Current-main proof:

  • src/agents/subagent-registry-lifecycle.ts still calls cleanup directly after the triggerCleanup check.
  • src/agents/subagent-registry-run-manager.ts currently resets endedHookEmittedAt but has no browser cleanup dispatch flag.
  • src/agents/subagent-registry.types.ts has no browserCleanupDispatchedAt field.
  • Local current-main sanity: pnpm test src/agents/subagent-registry-lifecycle.test.ts passes, but there is no current regression covering this double-cleanup case.

No blocking findings from me.

@vincentkoc vincentkoc added the triage: refactor-only Candidate: refactor/cleanup-only PR without maintainer context. label Apr 26, 2026
@clawsweeper

clawsweeper Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed May 29, 2026, 3:32 AM ET / 07:32 UTC.

Summary
Adds a per-run browserCleanupDispatchedAt marker, guards only cleanupBrowserSessionsForLifecycleEnd in subagent completion, resets the marker on rearm, and adds duplicate-completion lifecycle tests.

PR surface: Source +12, Tests +89. Total +101 across 4 files.

Reproducibility: yes. Source inspection shows the in-process listener and gateway wait path both converge on completeSubagentRun(..., triggerCleanup: true), and the PR body supplies before/after live output plus focused regression tests; I did not execute tests in this read-only review.

Review metrics: 1 noteworthy metric.

  • Persisted run marker surface: 1 optional marker added, 1 rearm reset added. SubagentRunRecord is persisted state, so maintainers should notice that old records safely omit the marker and restarted runs explicitly clear it.

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

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

Risk before merge

Maintainer options:

  1. Decide the mitigation before merge
    Land this narrow dispatch guard after required checks and maintainer merge validation, then rebase or adjust broader lifecycle cleanup work around the landed behavior.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] No repair lane is needed because I found no actionable patch defect; maintainers should handle ordinary merge validation and landing-order coordination with the broader lifecycle PR.

Security
Cleared: The diff changes agents lifecycle bookkeeping and tests only; it does not touch secrets, permissions, dependencies, CI, install scripts, lockfiles, or publishing surfaces.

Review details

Best possible solution:

Land this narrow dispatch guard after required checks and maintainer merge validation, then rebase or adjust broader lifecycle cleanup work around the landed behavior.

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

Yes. Source inspection shows the in-process listener and gateway wait path both converge on completeSubagentRun(..., triggerCleanup: true), and the PR body supplies before/after live output plus focused regression tests; I did not execute tests in this read-only review.

Is this the best way to solve the issue?

Yes. The PR is the narrowest maintainable fix I found: it dedupes only the browser cleanup wrapper, leaves retire/announce finalization available to every caller, and resets the marker on rearm.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 854cb9292db8.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix live console output comparing 100 pre/post production-style parallel-completion trials, and the latest head has a successful real behavior proof check.

Label justifications:

  • P2: This is a normal-priority agents lifecycle bug fix with limited blast radius and focused regression coverage.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes after-fix live console output comparing 100 pre/post production-style parallel-completion trials, and the latest head has a successful real behavior proof check.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix live console output comparing 100 pre/post production-style parallel-completion trials, and the latest head has a successful real behavior proof check.
Evidence reviewed

PR surface:

Source +12, Tests +89. Total +101 across 4 files.

View PR surface stats
Area Files Added Removed Net
Source 3 26 14 +12
Tests 1 89 0 +89
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 4 115 14 +101

What I checked:

  • Repository policy read: Full root policy and scoped agents policy were read; the relevant guidance was to review the agent lifecycle path beyond the diff and treat proof/validation carefully. (AGENTS.md:1, 854cb9292db8)
  • Current main cleanup is unguarded: On current main, completeSubagentRun calls cleanupBrowserSessionsForLifecycleEnd whenever cleanup is triggered, then continues to runtime retirement and announce cleanup with no browser-cleanup dispatch flag. (src/agents/subagent-registry-lifecycle.ts:1250, 854cb9292db8)
  • Dual completion path exists: The run manager ensures the listener, persists the run, starts the sweeper, and also starts gateway waitForSubagentCompletion; the comment identifies the in-process lifecycle listener as the embedded-run fallback. (src/agents/subagent-registry-run-manager.ts:719, 854cb9292db8)
  • Lifecycle listener also completes runs: The registry wires an in-process lifecycle listener and uses the shared lifecycle controller's completeSubagentRun, matching the reported two callers converging on the same cleanup branch. (src/agents/subagent-registry.ts:1048, 854cb9292db8)
  • Low-level tab close is already drain-idempotent: The browser session tab registry takes tracked tabs by deleting each session's tab map before closing, so the PR is correctly scoped to wrapper/facade duplicate dispatch rather than changing CDP close semantics. (extensions/browser/src/browser/session-tab-registry.ts:147, 854cb9292db8)
  • PR scopes the new guard narrowly: The PR head wraps only the browser cleanup wrapper in the new dispatch check; the runtime retirement and announce cleanup tail remain after the guarded block. (src/agents/subagent-registry-lifecycle.ts:1247, abbb467d63cf)

Likely related people:

  • steipete: Recent GitHub history shows multiple substantial subagent lifecycle/run-manager commits in the same files, including cleanup and wait-path hardening that this PR builds around. (role: recent area contributor; confidence: high; commits: e01a885d1833, 5d81c29cc4f4, d73f3ac85d5f; files: src/agents/subagent-registry-lifecycle.ts, src/agents/subagent-registry-run-manager.ts, src/agents/subagent-registry.types.ts)
  • vincentkoc: Authored the related duplicate subagent_ended hook guard that the PR uses as the closest sibling lifecycle race pattern. (role: adjacent race fixer; confidence: medium; commits: 48042c387556, a1b656705992; files: src/agents/subagent-registry-lifecycle.ts, src/agents/subagent-registry.types.ts)
  • Onur: Introduced the thread-bound subagent registry/types foundation that the current completion lifecycle paths evolved from. (role: feature introducer; confidence: medium; commits: 8178ea472db1; files: src/agents/subagent-registry.ts, src/agents/subagent-registry.types.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.

@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch from 00cab42 to cb3e21b Compare May 6, 2026 02:12
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 6, 2026
@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch from 81d901e to 4abfc02 Compare May 6, 2026 08:10
@Feelw00

Feelw00 commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper review

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 6, 2026
@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch from 17ccc9b to 7067f30 Compare May 11, 2026 00:17
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 11, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 11, 2026
@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch from 7067f30 to 352bc76 Compare May 19, 2026 05:22
@Feelw00

Feelw00 commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current upstream/main (352bc76586 head) to clear the merge conflict that accumulated since 2026-04-25. Two conflicts resolved:

  • subagent-registry-lifecycle.ts: kept the upstream try/catch wrap from e01a885d18 (complete ended subagent cleanup after helper failures) and placed the dedupe guard ahead of it. Duplicate completion paths still short-circuit before the wrapper, and any helper failure inside is caught downstream.
  • subagent-registry.types.ts: kept the new upstream delivery/suspension fields alongside browserCleanupDispatchedAt.

Local regression test green: pnpm test src/agents/subagent-registry-lifecycle.test.ts 23/23.

@greptile review and provide confidence score
@clawsweeper review

@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
@clawsweeper

clawsweeper Bot commented May 19, 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 the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
@Feelw00

Feelw00 commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review and provide confidence score

@clawsweeper

clawsweeper Bot commented May 20, 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. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels May 20, 2026
@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch from d4276de to 526e641 Compare May 29, 2026 00:25
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 29, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. 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 May 29, 2026
@clawsweeper

clawsweeper Bot commented May 29, 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:

@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch from 526e641 to e3e70aa Compare May 29, 2026 01:03
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 29, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 29, 2026
@RomneyDa

Copy link
Copy Markdown
Member

Heads up: this PR needs to be updated against current main before the new required Dependency Guard check can pass.

Feelw00 added 2 commits May 29, 2026 16:13
…patch flag

completeSubagentRun can be invoked in parallel from two completion paths for
the same runId: the in-process listener (registry.ts phase='end') and the
gateway waitForSubagentCompletion RPC (run-manager.ts). registerSubagentRun
pairs both unconditionally, so embedded subagent completion invokes the
cleanupBrowserSessionsForLifecycleEnd wrapper twice for the same
childSessionKey.

Actual CDP browserCloseTab IPC is already idempotent — the bundled browser
extension drains tracked tabs via takeTrackedTabsForSessionKeys, so the second
call short-circuits with tabs.length === 0. However both wrapper invocations
still pay plugin-sdk facade lazy-load, normalizeSessionKeys, and
runBestEffortCleanup overhead, and the guard closes a defense-in-depth gap if
the drain pattern is ever removed or replaced.

Add SubagentRunRecord.browserCleanupDispatchedAt and a sync check-then-set in
completeSubagentRun that matches the endedHookEmittedAt dedup flag introduced
in 48042c3 for the sibling hook-emit race. Reset the flag at rearm time in
run-manager.ts alongside endedHookEmittedAt.

[AI-assisted]
…per only

ClawSweeper re-review flagged [P2] "Keep duplicate completions on the
finalization path": the browserCleanupDispatchedAt guard returned from
completeSubagentRun entirely, so a duplicate completion caller skipped
retireRunModeBundleMcpRuntime and startSubagentAnnounceCleanupFlow, not just
the browser-cleanup wrapper. On pre-fix main the duplicate caller still ran
that tail because its own cleanupBrowserSessionsForLifecycleEnd returns
immediately once takeTrackedTabsForSessionKeys has drained the tabs.

Scope the check-then-set guard to the cleanupBrowserSessions wrapper only.
retireRunModeBundleMcpRuntime (idempotent) and startSubagentAnnounceCleanupFlow
(single-caller via beginSubagentCleanup) now run for every completion caller,
restoring pre-fix tail behavior while still firing the tab-close IPC once.

Add a held-first-cleanup regression test: with the first caller parked inside
a pending cleanup promise, a second completeSubagentRun must still reach
retireSessionMcpRuntimeForSessionKey and runSubagentAnnounceFlow.

[AI-assisted]
@Feelw00
Feelw00 force-pushed the fix/subagent-registry-browser-cleanup-dedup branch from e3e70aa to abbb467 Compare May 29, 2026 07:26
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 29, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 29, 2026
@Feelw00

Feelw00 commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

@RomneyDa
Done. Rebased onto current main (head abbb467); the Dependency Guard check is green now. Thanks for the heads up!

@steipete steipete self-assigned this May 30, 2026
@steipete

Copy link
Copy Markdown
Contributor

Maintainer landing verification for PR #68669.

Behavior addressed: duplicate embedded subagent completion callers no longer dispatch cleanupBrowserSessionsForLifecycleEnd twice for the same runId; duplicate callers still run the retire + announce tail.

Real environment tested: GitHub CI on PR head abbb467d63cf2f15070565e75c47ea6be056ff32; local maintainer checkout on main after git pull --ff-only to d11e82aeea.

Exact steps or command run after this patch:

  • git status -sb
  • git push (rejected because origin/main moved; followed by fast-forward pull, no local commits)
  • git pull --ff-only
  • gh pr view 68669 --repo openclaw/openclaw --json number,title,author,assignees,state,isDraft,mergeable,mergeStateStatus,headRefOid,baseRefOid,headRefName,headRepositoryOwner,maintainerCanModify,additions,deletions,changedFiles,statusCheckRollup,closingIssuesReferences,url
  • git fetch origin pull/68669/head
  • git merge-tree $(git merge-base origin/main FETCH_HEAD) origin/main FETCH_HEAD

Evidence after fix: PR CI run 26624223717 was green for the relevant required lanes, including checks-node-agentic-agents, build-artifacts, check-lint, check-prod-types, check-test-types, check-guards, and additional boundary checks. Dependency Guard, Real behavior proof, CodeQL, OpenGrep, and Socket checks were also green on the latest PR head.

Observed result after fix: source review confirms the new browserCleanupDispatchedAt check wraps only the browser cleanup wrapper; retireRunModeBundleMcpRuntime and startSubagentAnnounceCleanupFlow remain on the duplicate completion path. The PR's regression tests cover both the double-dispatch case and the held-first-cleanup duplicate-tail case.

What was not tested: I did not run a local branch checkout test or a live browser-backed embedded subagent run during landing; CI and the supplied real-behavior proof cover the patched controller path.

@steipete steipete left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved for landing. Narrow lifecycle guard, focused regression coverage, CI green on PR head, and local merge-tree against current main is clean.

@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 30, 2026
@steipete
steipete merged commit a9a86f7 into openclaw:main May 30, 2026
128 of 129 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. triage: refactor-only Candidate: refactor/cleanup-only PR without maintainer context.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagent-registry: cleanupBrowserSessionsForLifecycleEnd wrapper invoked twice for same runId in embedded mode

4 participants