Repository navigation
fix(whatsapp): fall back to no-breakaway detach flags when the gateway job forbids breakaway - #125117
liuhao1024 wants to merge 3 commits into
Conversation
…OB when the gateway job forbids breakaway The WhatsApp adapter spawned the Node bridge with windows_detach_popen_kwargs(), whose Windows flags include CREATE_BREAKAWAY_FROM_JOB. When the gateway itself sits in a job object that forbids breakaway (Hermes Desktop/Electron, Task Scheduler, service wrappers), CreateProcess rejects the call with WinError 5 and every 300s reconnect reuses the same rejected flags, so WhatsApp never comes up for the lifetime of that gateway (NousResearch#68128). Fall back to windows_detach_flags_without_breakaway() on OSError — the same try/except pattern gateway_windows._spawn_detached() already uses — preserving breakaway where the job allows it. POSIX keeps start_new_session=True.
The retry shape matches 1. The breakaway stamp is missing, so a fallback-spawned bridge is indistinguishable from a job-teardown kill.
2.
Deterministic run — one stub
The two Minor: Unverified: I drove the two code paths directly rather than the full suite, and the |
… back without breakaway too
Both bridge spawn attempts now stamp _WINDOWS_GATEWAY_BREAKAWAY_ENV ("1"/"0"),
matching hermes_cli/gateway.py and gateway_windows._spawn_detached — a fallback-
spawned bridge stays in the parent's job, and without the stamp a job-teardown
kill is indistinguishable from a clean exit. _spawn_detached_update gets the
same no-breakaway retry as the run_shutdown restart watcher, so /update no
longer dies with PermissionError after .update_pending.json was written.
|
Both points taken and fixed in af30605: 1. Breakaway stamp. Both bridge attempts now stamp 2. Minor (error_code). Kept as log-only deliberately: any bundled_app sites. Left out of this PR — they're Electron-launch-path surfaces I can't exercise here; the fix shape is now in three reviewed places, so a follow-up on those two sites is mechanical. |
Both round-1 points are closed on live head Breakaway stamp.
Mutation, on the real head tree with One boundary, the unstated one. The consumer validates the value, not mere presence: Verified: executed both test files and three mutations on the head tree. Unverified: no real job object — the |
…idge child bridge.js never reads _HERMES_GATEWAY_BREAKAWAY; the value is consumed by the respawned gateway's breakaway-state probe. Note it at the stamp site so a future reader does not expect the bridge to act on it.
|
Addressed in a309e4d — added the note at the stamp site: Suite re-run on the new head: 16 passed / 5 skipped (Windows-only cases skip on macOS), same totals as before the comment. |
Confirmed — and the delta is exactly what you describe. Worth recording why that wording is the useful one: the reader's mistake here is not "the stamp is missing" but "this stamp controls the bridge". Naming the consumer and the actual payoff site ( Your 16 passed / 5 skipped on macOS is your measurement — the skip count is the Windows-only cases, so it is not comparable to the 16 passed / 2 skipped I measured on Windows last round, and I have not re-run this head. The three mutations stay as recorded: dropping the stamp on both attempts, stamping the retry Unverified: no real job object on this host — the |
|
Agreed on why the wording earns its place: the reader who trips here is not looking for a missing stamp, they are looking for the consumer in the wrong process — naming |
What does this PR do?
When the gateway runs inside a Windows job object that forbids breakaway (Hermes Desktop/Electron, Task Scheduler, service wrappers), the WhatsApp bridge spawn fails with
WinError 5on every reconnect becausewindows_detach_popen_kwargs()always requestsCREATE_BREAKAWAY_FROM_JOB. The adapter then retries forever with the same rejected flags, so WhatsApp never comes up for the lifetime of that gateway process.This adds the documented fallback for that case: on
OSErrorfrom the breakaway spawn, retry once withwindows_detach_flags_without_breakaway()— the same try/except patterngateway_windows._spawn_detached()already uses (thewindows_detach_flags()docstring documents this exact contract). Breakaway is preserved where the job allows it; only the rejected spawn is retried without the bit. POSIX behavior is unchanged (start_new_session=True).Note vs #65990: that PR swaps the same spawn site to
windows_detach_flags_without_breakaway()unconditionally; this PR keeps breakaway when the job permits it and only degrades on rejection (the approach #68128 asks for and triage flagged as needing a maintainer choice). It is also rebased on currentmain, where the spawn site has moved toadapter.py:537.Related Issue
Fixes #68128
Type of Change
Changes Made
plugins/platforms/whatsapp/adapter.py: the bridgesubprocess.Popennow spawns viawindows_detach_flags()insidetry/except OSError, falling back towindows_detach_flags_without_breakaway()(with a warning naming the error code) when the gateway's job object forbids breakaway; POSIX keepsstart_new_session=True.tests/gateway/test_whatsapp_connect.py: newTestBreakawayFallbackcovering (1) breakaway denied → secondPopencall drops only theCREATE_BREAKAWAY_FROM_JOBbit andconnect()succeeds, (2) breakaway allowed → singlePopencall keeps the flag, (3) POSIX → singlePopencall withstart_new_session=Trueand nocreationflags.How to Test
pytest tests/gateway/test_whatsapp_connect.py -q— Observed result: 11 passed, 5 skipped (the 5 skips pre-date this change; they require the optional messaging transport).test_breakaway_denied_retries_without_breakawayandtest_breakaway_allowed_keeps_breakaway_flagsfail on unfixed code, pass with it.pytest tests/ -q --ignore=tests/e2e— Observed result: 644 passed, 16 skipped, 1 failed (tests/agent/test_anthropic_message_stop.py, unrelated to this change; it fails identically on a clean checkout ofmainwithout these commits).Bridge breakaway spawn failed (error=5); retrying without CREATE_BREAKAWAY_FROM_JOBwarning followed by a successful bridge start, instead ofFailed to start bridge: [WinError 5]every 300s — matching the in-job verification reported in WhatsApp bridge spawn fails with WinError 5 in no-breakaway job objects — adapter missing the documented windows_detach_flags_without_breakaway() fallback #68128 for this fallback approach.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass (one pre-existing failure intests/agent/test_anthropic_message_stop.pyreproduces identically on cleanmain; all other tests pass)Documentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AFor New Skills
N/A