fix(gateway): emit before_reset on session reset#53872
fix(gateway): emit before_reset on session reset#53872jalehman merged 6 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR brings the gateway Key changes:
Issues found:
Confidence Score: 3/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/gateway/session-reset-service.ts
Line: 272-275
Comment:
**Unguarded synchronous file I/O can abort the session reset**
`readSessionMessages` calls `fs.readFileSync` synchronously on line 108 of `session-utils.fs.ts`. There is a TOCTOU window between the `fs.existsSync` guard inside that function and the actual `readFileSync` call — if the file is removed in that window (e.g. during rapid session cycling or a concurrent archive step), `readFileSync` throws. Because the call here is outside the `void hookRunner.runBeforeReset(...).catch(...)` async boundary, that synchronous exception propagates straight up through `emitGatewayBeforeResetPluginHook` and into `performGatewaySessionReset`, aborting the reset entirely.
Compare this to the equivalent path in `commands-core.ts` (line 87–120), which wraps everything — including the async file read — inside a try-catch so that any I/O failure is just logged and the reset continues.
Suggested fix: wrap the `readSessionMessages` call in a try-catch so that file I/O failures degrade gracefully to an empty messages list rather than throwing:
```
let messages: unknown[] = [];
try {
if (typeof sessionId === "string" && sessionId.trim().length > 0) {
messages = readSessionMessages(sessionId, params.storePath, sessionFile);
}
} catch {
// log and continue with empty messages
logVerbose(`before_reset: failed to read session messages for ${sessionId ?? "(none)"}`);
}
```
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/gateway/session-reset-service.ts
Line: 272-275
Comment:
**Message shape diverges from the chat-command `before_reset` path**
`readSessionMessages` enriches each message with `__openclaw` metadata via `attachOpenClawTranscriptMeta` and also synthesises extra `role: "system"` entries for compaction checkpoints (see `session-utils.fs.ts` lines 130–144). The parallel path in `commands-core.ts` pushes raw `entry.message` objects directly, with no extra metadata and no compaction entries.
Plugins that already handle `before_reset` from the chat-command path (and therefore expect undecorated message objects with no compaction entries) will receive a different shape when the reset is triggered via the gateway. Consider either aligning `commands-core.ts` to also use `readSessionMessages`, or strip the `__openclaw` metadata before passing messages to the hook so the contract is consistent across both paths.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(gateway): emit before_reset on sessi..." | Re-trigger Greptile |
| const messages = | ||
| typeof sessionId === "string" && sessionId.trim().length > 0 | ||
| ? readSessionMessages(sessionId, params.storePath, sessionFile) | ||
| : []; |
There was a problem hiding this comment.
Message shape diverges from the chat-command
before_reset path
readSessionMessages enriches each message with __openclaw metadata via attachOpenClawTranscriptMeta and also synthesises extra role: "system" entries for compaction checkpoints (see session-utils.fs.ts lines 130–144). The parallel path in commands-core.ts pushes raw entry.message objects directly, with no extra metadata and no compaction entries.
Plugins that already handle before_reset from the chat-command path (and therefore expect undecorated message objects with no compaction entries) will receive a different shape when the reset is triggered via the gateway. Consider either aligning commands-core.ts to also use readSessionMessages, or strip the __openclaw metadata before passing messages to the hook so the contract is consistent across both paths.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/session-reset-service.ts
Line: 272-275
Comment:
**Message shape diverges from the chat-command `before_reset` path**
`readSessionMessages` enriches each message with `__openclaw` metadata via `attachOpenClawTranscriptMeta` and also synthesises extra `role: "system"` entries for compaction checkpoints (see `session-utils.fs.ts` lines 130–144). The parallel path in `commands-core.ts` pushes raw `entry.message` objects directly, with no extra metadata and no compaction entries.
Plugins that already handle `before_reset` from the chat-command path (and therefore expect undecorated message objects with no compaction entries) will receive a different shape when the reset is triggered via the gateway. Consider either aligning `commands-core.ts` to also use `readSessionMessages`, or strip the `__openclaw` metadata before passing messages to the hook so the contract is consistent across both paths.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Leaving this one out for now to keep the patch minimal. The concrete bug here was that before_reset was not reliably firing/saving on the reset paths we traced; the current fix is limited to preserving hook execution and avoiding false-positive/failed resets. Aligning the messages payload shape across chat-command and gateway paths would widen the hook contract surface, and this gateway path was not functioning end-to-end before this fix anyway. For the current consumer (memory-lancedb-pro), the shape difference is not material because it only summarizes normal user/assistant messages. If maintainers want the hook contract normalized, I would prefer to do that as a follow-up change.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3faba69c09
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7b3806c95d
ℹ️ 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".
|
Addressed the follow-up P1 in This moves gateway I also added a focused regression that mutates the store under the lock and proves |
260f2d2 to
e248c1b
Compare
e248c1b to
0a9e73a
Compare
This comment was marked as spam.
This comment was marked as spam.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e537bd0e53
ℹ️ 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 (typeof sessionId === "string" && sessionId.trim().length > 0) { | ||
| messages = readSessionMessages(sessionId, params.storePath, sessionFile); |
There was a problem hiding this comment.
Move transcript read off reset request thread
When before_reset hooks are enabled, sessions.reset now performs readSessionMessages(...) synchronously in the RPC path, and that helper walks the whole transcript via sync filesystem I/O and JSON parsing. On large transcripts this blocks the Node event loop, delaying unrelated gateway websocket traffic and making reset latency spike; this is especially visible in production where sessions can be long-lived. The hook dispatch can stay fire-and-forget, but the transcript load should not run as synchronous work on the request path.
Useful? React with 👍 / 👎.
e537bd0 to
460408c
Compare
ebc8b17 to
f004c5f
Compare
f004c5f to
a47894e
Compare
|
Merged via squash.
Thanks @VACInc! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a47894ef16
ℹ️ 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".
| const sessionId = params.entry?.sessionId; | ||
| const sessionFile = params.entry?.sessionFile; | ||
| const agentId = normalizeAgentId(params.target.agentId ?? resolveDefaultAgentId(params.cfg)); | ||
| const workspaceDir = resolveAgentWorkspaceDir(params.cfg, agentId); |
There was a problem hiding this comment.
Use session workspace in before_reset hook context
emitGatewayBeforeResetPluginHook always sets workspaceDir from the agent default workspace, but gateway sessions can carry a per-session spawnedWorkspaceDir (for subagent sessions) that is preserved across resets. In that case, before_reset plugins receive the wrong workspace path and can read/write memory or artifacts in the parent agent workspace instead of the session’s actual workspace when sessions.reset is called on subagent keys.
Useful? React with 👍 / 👎.
| const archived = (await fs.readdir(dir)) | ||
| .filter((name) => name.startsWith(resetPrefix)) | ||
| .toSorted(); | ||
| const latest = archived[archived.length - 1]; |
There was a problem hiding this comment.
Match archived transcript to the reset source session
The ENOENT fallback picks the lexicographically newest *.reset.* sibling, which is not tied to the specific previousSessionEntry being processed. Because emitResetCommandHooks runs this work fire-and-forget, back-to-back /new or /reset commands can produce multiple archives before an earlier hook reads, so that earlier hook may ingest a later reset’s transcript and persist/dedupe against the wrong conversation history.
Useful? React with 👍 / 👎.
Summary
before_resetplugin hook from the gateway session reset pathsessions.reset->before_resetemissionWhy
Gateway
/newand/resetalready emitted the legacy internal session hook, but they did not emit the newer typed pluginbefore_resethook. That meant plugins migrated tobefore_resetwere skipped on the gateway reset path even though the chat command path already supported the hook.Testing
pnpm exec vitest run src/gateway/server.sessions.gateway-server-sessions-a.test.ts -t "sessions.reset emits"