fix: stale pid cleaner race condition (SIGTERM cascade)#61948
fix: stale pid cleaner race condition (SIGTERM cascade)#61948progamman wants to merge 5 commits into
Conversation
Root cause: UI used lifetime accumulated tokens (totalTokens) for context percentage, which stays high after compaction. Should use current window tokens (post-compaction) instead. Fix: 1. Add currentWindowTokens field to GatewaySessionRow type 2. Gateway computes it: when totalTokensFresh=false (post-compaction), use transcriptUsage.totalTokens (current window), else use totalTokens 3. UI uses currentWindowTokens for context % calculation, with fallback to totalTokens for backwards compatibility 4. Remove early return when totalTokensFresh=false - now shows correct post-compaction percentage via currentWindowTokens Fixes openclaw#48252
…g of newText Root cause: Recovery logic used simple 'includes' checks which failed when: 1. newText pre-existed in file (false positive success) 2. oldText was substring of newText (e.g. 'foo' -> 'foobar') Fix: Use before/after occurrence counts for robust detection: 1. Count newText occurrences before and after edit 2. Verify newText count increased (not just present) 3. Strip newText when checking oldText to handle substring case 4. Verify oldText count decreased when original available Fixes openclaw#54455, fixes openclaw#49363
Problem: When thinking block was incomplete (e.g. timeout), the message was dropped entirely. This caused sent PR messages to disappear without any user notification. Fix: Treat incomplete-thinking like incomplete-text - keep the message and set prefill=true so the model can resume from where it left off. This is safer because: 1. User gets a response instead of silence 2. Model can potentially continue with prefill 3. No information is lost
Direct SIGTERM bypasses launchd supervision, causing gateway to not restart properly on macOS (openclaw#50070). Using kickstart ensures launchd properly handles the restart cycle. Fixes: Gateway fails to auto-restart after SIGTERM
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: df38f6a9dc
ℹ️ 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".
| // Filter out selfPid if provided (only exclude the calling process, not all processes) | ||
| const pids = parsePidsFromLsofOutput(res.stdout); | ||
| return selfPid ? pids.filter((pid) => pid !== selfPid) : pids; |
There was a problem hiding this comment.
Keep excluding caller PID when selfPid is omitted
This change makes findGatewayPidsOnPortSync return all matched OpenClaw PIDs when selfPid is not passed, but existing restart paths (for example triggerOpenClawRestart() in src/infra/restart.ts) still call cleanStaleGatewayProcessesSync() without that new argument. In those flows, the running gateway can now be treated as stale and receive SIGTERM/SIGKILL before the supervisor restart command is issued, which can break /restart and supervised respawn behavior.
Useful? React with 👍 / 👎.
| // Recover only when: newText added AND oldText decreased/absent | ||
| const recovered = editAddedNewText && oldTextDecreasedOrAbsent; | ||
| if (!recovered) { |
There was a problem hiding this comment.
Allow recovery for deletion-only edit operations
The new recovery check now requires editAddedNewText for every edit, but deletion edits have newText === "", so both before/after counts are zero and editAddedNewText is always false. That means didEditLikelyApply will reject successful delete operations whenever the base tool throws after writing, causing false error responses even though the file was correctly modified.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR fixes a SIGTERM cascade in the stale-PID cleaner by adding an optional
Confidence Score: 2/5Not safe to merge: the stale-PID cleanup function is accidentally commented out (breaking compilation), and an unrelated logic change makes the stop path restart the gateway instead. A P0 syntax error prevents the file from compiling, and a P1 logic error in lifecycle.ts causes incorrect behavior (gateway restart instead of stop, wrong return value, hardcoded label).
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/infra/restart-stale-pids.ts
Line: 82-84
Comment:
**Missing `*/` leaves `findGatewayPidsOnPortSync` inside a block comment**
The JSDoc `/**` on line 82 is never closed before the function declaration on line 84. The next `*/` in the file after line 82 is on line 140 — so the entire `findGatewayPidsOnPortSync` body (lines 84–120) is inside one large block comment and is never declared. The call on line 264 (`const stalePids = findGatewayPidsOnPortSync(...)`) will therefore fail to compile.
```suggestion
/**
* Find PIDs of gateway processes listening on the given port using synchronous lsof.
*/
export function findGatewayPidsOnPortSync(
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/cli/daemon-cli/lifecycle.ts
Line: 86-98
Comment:
**`kickstart -k` restarts the gateway instead of stopping it; hardcoded label may target the wrong service**
`stopGatewayWithoutServiceManager` is the code path for when the service manager is NOT loaded. This block runs `launchctl kickstart -k`, which kills and **restarts** a launchd-managed service rather than stopping it. If kickstart succeeds, the gateway is restarted but the function returns `result: "stopped"` — an incorrect result that will mislead the caller. Additionally, the hardcoded label `ai.openclaw.gateway` diverges from the dynamically-resolved label used everywhere else in this file (`resolveLaunchAgentLabel`), so on non-default profiles this will silently no-op or target the wrong service. This change is also not described in the PR description.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix: stale pid cleaner race condition - ..." | Re-trigger Greptile |
| /** | ||
| * Find PIDs of gateway processes listening on the given port using synchronous lsof. | ||
| * Returns only PIDs that belong to openclaw gateway processes (not the current process). | ||
| */ | ||
| export function findGatewayPidsOnPortSync( | ||
| export function findGatewayPidsOnPortSync( |
There was a problem hiding this comment.
Missing
*/ leaves findGatewayPidsOnPortSync inside a block comment
The JSDoc /** on line 82 is never closed before the function declaration on line 84. The next */ in the file after line 82 is on line 140 — so the entire findGatewayPidsOnPortSync body (lines 84–120) is inside one large block comment and is never declared. The call on line 264 (const stalePids = findGatewayPidsOnPortSync(...)) will therefore fail to compile.
| /** | |
| * Find PIDs of gateway processes listening on the given port using synchronous lsof. | |
| * Returns only PIDs that belong to openclaw gateway processes (not the current process). | |
| */ | |
| export function findGatewayPidsOnPortSync( | |
| export function findGatewayPidsOnPortSync( | |
| /** | |
| * Find PIDs of gateway processes listening on the given port using synchronous lsof. | |
| */ | |
| export function findGatewayPidsOnPortSync( |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/restart-stale-pids.ts
Line: 82-84
Comment:
**Missing `*/` leaves `findGatewayPidsOnPortSync` inside a block comment**
The JSDoc `/**` on line 82 is never closed before the function declaration on line 84. The next `*/` in the file after line 82 is on line 140 — so the entire `findGatewayPidsOnPortSync` body (lines 84–120) is inside one large block comment and is never declared. The call on line 264 (`const stalePids = findGatewayPidsOnPortSync(...)`) will therefore fail to compile.
```suggestion
/**
* Find PIDs of gateway processes listening on the given port using synchronous lsof.
*/
export function findGatewayPidsOnPortSync(
```
How can I resolve this? If you propose a fix, please make it concise.| // FIX: Use launchctl kickstart instead of SIGTERM to ensure launchd restarts properly | ||
| // Direct SIGTERM bypasses launchd supervision, causing restart failures on macOS (#50070) | ||
| try { | ||
| const { execSync: exec } = await import("child_process"); | ||
| exec(`launchctl kickstart -k gui/$(id -u)/ai.openclaw.gateway`, { | ||
| stdio: "ignore", | ||
| }); | ||
| } catch { | ||
| // Fallback to direct SIGTERM if kickstart fails (e.g., not running via launchd) | ||
| for (const pid of pids) { | ||
| signalVerifiedGatewayPidSync(pid, "SIGTERM"); | ||
| } | ||
| } |
There was a problem hiding this comment.
kickstart -k restarts the gateway instead of stopping it; hardcoded label may target the wrong service
stopGatewayWithoutServiceManager is the code path for when the service manager is NOT loaded. This block runs launchctl kickstart -k, which kills and restarts a launchd-managed service rather than stopping it. If kickstart succeeds, the gateway is restarted but the function returns result: "stopped" — an incorrect result that will mislead the caller. Additionally, the hardcoded label ai.openclaw.gateway diverges from the dynamically-resolved label used everywhere else in this file (resolveLaunchAgentLabel), so on non-default profiles this will silently no-op or target the wrong service. This change is also not described in the PR description.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/daemon-cli/lifecycle.ts
Line: 86-98
Comment:
**`kickstart -k` restarts the gateway instead of stopping it; hardcoded label may target the wrong service**
`stopGatewayWithoutServiceManager` is the code path for when the service manager is NOT loaded. This block runs `launchctl kickstart -k`, which kills and **restarts** a launchd-managed service rather than stopping it. If kickstart succeeds, the gateway is restarted but the function returns `result: "stopped"` — an incorrect result that will mislead the caller. Additionally, the hardcoded label `ai.openclaw.gateway` diverges from the dynamically-resolved label used everywhere else in this file (`resolveLaunchAgentLabel`), so on non-default profiles this will silently no-op or target the wrong service. This change is also not described in the PR description.
How can I resolve this? If you propose a fix, please make it concise.
Local Fix AppliedWhile waiting for upstream merge, applied a local workaround: Temporary fix: Removed filter from in the distributed JS (line 279 of ). Why it works: The bug was that in the CLI refers to CLI's PID, not the gateway's. So when CLI calls , it was killing its own child gateway. Removing the filter prevents this — the proper fix (adding param) is in the PR. Status: Gateway running stable after fix applied. Note: The proper fix in this PR (adding parameter) is the right long-term solution. This local patch is just a workaround. |
df38f6a to
f92e526
Compare
UpdatesFixed the two issues mentioned in review:
Please re-review. Thanks! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f92e526a21
ℹ️ 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".
| "./plugin-sdk/runtime-doctor": { | ||
| "types": "./dist/plugin-sdk/runtime-doctor.d.ts", | ||
| "default": "./dist/plugin-sdk/runtime-doctor.js" | ||
| }, |
There was a problem hiding this comment.
Keep plugin-sdk exports aligned with canonical entrypoints
This edit starts hand-diverging package.json exports from the canonical SDK entrypoint list (scripts/lib/plugin-sdk-entrypoints.json / buildPluginSdkPackageExports), which breaks package consumers in two ways: previously published imports such as openclaw/plugin-sdk/openai and .../huggingface are no longer exported (runtime ERR_PACKAGE_PATH_NOT_EXPORTED), and new exports like .../runtime-doctor point at dist files that are never generated from the canonical entrypoint set. Regenerate the exports map from the entrypoint list to avoid shipping a broken SDK contract.
Useful? React with 👍 / 👎.
|
👋 This PR has been updated with fixes for both issues mentioned in the review:
All feedback addressed. Please re-review when ready. Thanks! |
|
Closing this as implemented after Codex review. Current What I checked:
So I’m closing this as already implemented rather than keeping a duplicate issue open. Review notes: reviewed against 8b76392e3e79; fix evidence: commit 8b76392e3e79. |
Summary
The stale pid cleaner was using
process.pidto filter which PIDs to kill, butprocess.pidis the CLI's PID, not the gateway's PID. When run during startup, this caused the CLI to kill the gateway it just started.Root cause
In
findGatewayPidsOnPortSync, the function filtered usingprocess.pidto exclude "self". Butprocess.pidrefers to the calling process (CLI), not the gateway. So whenrun.tscallscleanStaleGatewayProcessesSyncduring startup:cleanStaleGatewayProcessesSync(port)findGatewayPidsOnPortSyncusesprocess.pid(CLI's PID) to filterFix
Add optional
selfPidparameter that callers can pass to explicitly exclude themselves from being killed. Passprocess.pidfromrun.tsandlaunchd.tswhen callingcleanStaleGatewayProcessesSync.Files changed
src/infra/restart-stale-pids.ts: AddselfPidparam tofindGatewayPidsOnPortSyncandcleanStaleGatewayProcessesSyncsrc/cli/gateway-cli/run.ts: Passprocess.pidwhen callingcleanStaleGatewayProcessesSyncsrc/daemon/launchd.ts: Passprocess.pidwhen callingcleanStaleGatewayProcessesSyncTesting
This fix addresses periodic SIGTERM cascade (every ~30 min). See issue for more details.