fix: prevent orphaned session locks after cron job completion#31867
fix: prevent orphaned session locks after cron job completion#31867YUJIE2002 wants to merge 4 commits into
Conversation
…annel When sessions_send dispatches a message, it uses channel='webchat' (INTERNAL_MESSAGE_CHANNEL) as the transport. The agent method handler in server-methods/agent.ts would fall back to this transport channel when building the session entry patch (entry?.channel ?? request.channel), overwriting the session's real channel (e.g. 'telegram') to 'webchat'. This broke subsequent message delivery — the Telegram session would stop receiving messages because the routing layer saw it as a 'webchat' session. Fix: When inputProvenance.kind is 'inter_session', do not use the request's channel as a fallback for the session channel field, and do not propagate it as the turn's source channel for delivery routing. The internal transport channel is now treated as opaque — it never leaks into the session's identity or delivery metadata. Closes openclaw#31671
When entry.channel is absent during an inter-session send, the previous
code wrote channel: undefined into the patch. mergeSessionEntry uses
object spread ({...existing, ...patch}), so an explicit undefined value
clobbers a concurrently written real channel. Fix by conditionally
spreading the channel field — omit it entirely when there is nothing
to preserve.
Addresses review feedback from Codex (P1).
- Add LOCK_ACQUISITION_REJECT_THRESHOLD_MS to reject locks held >5min - Add comprehensive logging for lock acquire/release in store.ts - Add try-catch around lock.release() in attempt.ts and compact.ts - Fixes race condition where locks weren't released after cron runs
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01170c29dd
ℹ️ 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".
| if (inspected.ageMs !== null && inspected.ageMs > LOCK_ACQUISITION_REJECT_THRESHOLD_MS) { | ||
| const owner = typeof payload?.pid === "number" ? `pid=${payload.pid}` : "unknown"; | ||
| throw new Error( | ||
| `session file locked by ${owner} for ${Math.round(inspected.ageMs / 1000)}s (rejecting acquisition, threshold: ${LOCK_ACQUISITION_REJECT_THRESHOLD_MS}ms): ${lockPath}`, | ||
| { cause: err }, |
There was a problem hiding this comment.
Reclaim stale lock files before aborting acquisition
This guard throws immediately when a contended lock is older than 5 minutes, but it executes before the existing stale-lock recovery path (shouldReclaimContendedLockFile) can run. If a crashed process leaves behind an old lock file (the exact scenario this code is trying to recover from), acquisition now always fails fast and the stale lock is never removed, which can block subsequent session writes until someone manually cleans up the lock.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR bundles three fixes: session lock management improvements (main focus), and channel corruption prevention for inter-session messages. The lock changes add error handling around Confidence Score: 1/5
Last reviewed commit: 69cff09 |
| // Check if lock is too old to even attempt acquisition - prevent deadlock | ||
| if (inspected.ageMs !== null && inspected.ageMs > LOCK_ACQUISITION_REJECT_THRESHOLD_MS) { | ||
| const owner = typeof payload?.pid === "number" ? `pid=${payload.pid}` : "unknown"; | ||
| throw new Error( | ||
| `session file locked by ${owner} for ${Math.round(inspected.ageMs / 1000)}s (rejecting acquisition, threshold: ${LOCK_ACQUISITION_REJECT_THRESHOLD_MS}ms): ${lockPath}`, | ||
| { cause: err }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Reject threshold blocks reclamation of orphaned locks with dead PIDs
The age check happens before shouldReclaimContendedLockFile, preventing locks with dead PIDs from being reclaimed. If a process crashes leaving a 6-minute-old lock, this throws an error instead of cleaning it up via the existing reclaim logic (line 496-498), permanently blocking the session.
Fix: only reject if inspected.pidAlive === true, or move this check after the reclaim logic
| // Check if lock is too old to even attempt acquisition - prevent deadlock | |
| if (inspected.ageMs !== null && inspected.ageMs > LOCK_ACQUISITION_REJECT_THRESHOLD_MS) { | |
| const owner = typeof payload?.pid === "number" ? `pid=${payload.pid}` : "unknown"; | |
| throw new Error( | |
| `session file locked by ${owner} for ${Math.round(inspected.ageMs / 1000)}s (rejecting acquisition, threshold: ${LOCK_ACQUISITION_REJECT_THRESHOLD_MS}ms): ${lockPath}`, | |
| { cause: err }, | |
| ); | |
| } | |
| // Check if lock is too old to even attempt acquisition - prevent deadlock | |
| if (inspected.ageMs !== null && inspected.ageMs > LOCK_ACQUISITION_REJECT_THRESHOLD_MS && inspected.pidAlive) { | |
| const owner = typeof payload?.pid === "number" ? `pid=${payload.pid}` : "unknown"; | |
| throw new Error( | |
| `session file locked by ${owner} for ${Math.round(inspected.ageMs / 1000)}s (rejecting acquisition, threshold: ${LOCK_ACQUISITION_REJECT_THRESHOLD_MS}ms): ${lockPath}`, | |
| { cause: err }, | |
| ); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-write-lock.ts
Line: 487-494
Comment:
Reject threshold blocks reclamation of orphaned locks with dead PIDs
The age check happens before `shouldReclaimContendedLockFile`, preventing locks with dead PIDs from being reclaimed. If a process crashes leaving a 6-minute-old lock, this throws an error instead of cleaning it up via the existing reclaim logic (line 496-498), permanently blocking the session.
Fix: only reject if `inspected.pidAlive === true`, or move this check after the reclaim logic
```suggestion
// Check if lock is too old to even attempt acquisition - prevent deadlock
if (inspected.ageMs !== null && inspected.ageMs > LOCK_ACQUISITION_REJECT_THRESHOLD_MS && inspected.pidAlive) {
const owner = typeof payload?.pid === "number" ? `pid=${payload.pid}` : "unknown";
throw new Error(
`session file locked by ${owner} for ${Math.round(inspected.ageMs / 1000)}s (rejecting acquisition, threshold: ${LOCK_ACQUISITION_REJECT_THRESHOLD_MS}ms): ${lockPath}`,
{ cause: err },
);
}
```
How can I resolve this? If you propose a fix, please make it concise.- hooks.md: update bootstrap file list to include all 5 files (AGENTS, SOUL, TOOLS, IDENTITY, USER) - session-write-lock.ts: only reject acquisition if lock >5min AND PID is alive - If PID is dead (orphaned lock), allow reclamation instead of permanent deadlock
69cff09 to
2bcddf8
Compare
Problem
Session lock files are not being released after cron job completion, causing complete system paralysis. Lock is never released even when isError=false.
Root Cause
Fix
Testing
Closes #31749