fix: fire before_reset plugin hook from gateway sessions.reset#29969
fix: fire before_reset plugin hook from gateway sessions.reset#29969mcaxtr wants to merge 2 commits intoopenclaw:mainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e567df43e8
ℹ️ 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".
|
@greptile please re-review this PR. |
|
@greptile review |
Greptile SummaryThis PR fixes a bug where plugin Key implementation details:
Testing:
Evolution: Confidence Score: 5/5
Last reviewed commit: 585c5e3 |
|
Fixed in 22c3fdc — transcript is now read eagerly (awaited) before the fire-and-forget @greptile please re-review this PR. |
|
Codex Review: Didn't find any major issues. Another round soon, please! ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
|
@greptile please re-review this PR. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b2539f9e2
ℹ️ 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: a6456b684e
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a6456b684e
ℹ️ 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".
|
@greptile please re-review this PR. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 585c5e3c7f
ℹ️ 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".
|
@greptile please re-review this PR. |
Review note: Codex vs Greptile contradictionThe last two review cycles revealed a direct contradiction between the two bots:
The current approach (585c5e3) is the correct trade-off:
This is the only solution that avoids both the archival race AND keeps the slow hook dispatch off the response path. @greptile @chatgpt-codex-connector please re-review this PR. |
|
Codex Review: Didn't find any major issues. 🚀 ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
585c5e3 to
c1934b0
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c1934b0195
ℹ️ 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".
c1934b0 to
5f73cae
Compare
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟡 Resource exhaustion in sessions.reset before_reset hook due to unbounded transcript read/parse
DescriptionThe
Vulnerable code: const content = await fs.promises.readFile(sessionFile, "utf-8");
for (const line of content.split("\n")) {
...
const parsed = JSON.parse(line);
if (parsed.type === "message" && parsed.message) {
messages.push(parsed.message);
}
}RecommendationAvoid reading/splitting the entire transcript in the request path. Prefer streaming + hard limits (bytes/lines/messages) and consider truncating to the last N messages. Example: stream JSONL lines with a maximum file size and message cap: import { createReadStream } from "node:fs";
import readline from "node:readline";
const MAX_TRANSCRIPT_BYTES = 5 * 1024 * 1024; // tune
const MAX_MESSAGES = 200; // tune
const stat = await fs.promises.stat(sessionFile);
if (stat.size > MAX_TRANSCRIPT_BYTES) {
// Either skip, or only read the tail, or pass a truncated indicator
}
const messages: unknown[] = [];
const rl = readline.createInterface({
input: createReadStream(sessionFile, { encoding: "utf-8" }),
crlfDelay: Infinity,
});
for await (const line of rl) {
if (messages.length >= MAX_MESSAGES) break;
if (!line.trim()) continue;
try {
const parsed = JSON.parse(line);
if (parsed?.type === "message" && parsed.message) messages.push(parsed.message);
} catch {
// skip malformed lines
}
}Additionally:
2. 🔵 Arbitrary file read via unvalidated sessionFile path in sessions.reset before_reset hook
DescriptionIn
Vulnerable code: const sessionFile = entry?.sessionFile;
...
const content = await fs.promises.readFile(sessionFile, "utf-8");RecommendationTreat Recommended changes:
import path from "node:path";
import { resolveSessionFilePath } from "../../config/sessions/paths.js";
const sessionsDir = path.dirname(storePath);
const safeTranscriptPath = entry?.sessionId
? resolveSessionFilePath(entry.sessionId, { sessionFile }, { sessionsDir, agentId: target.agentId })
: null;
import { openBoundaryFile } from "../../infra/boundary-file-read.js";
const opened = await openBoundaryFile({
absolutePath: safeTranscriptPath,
rootPath: sessionsDir,
boundaryLabel: "sessions dir",
maxBytes: 2 * 1024 * 1024, // example cap
});
if (!opened.ok) {
// fire hook with empty messages
} else {
try {
const content = fs.readFileSync(opened.fd, "utf-8");
// parse jsonl...
} finally {
fs.closeSync(opened.fd);
}
}
Analyzed PR: #29969 at commit Last updated on: 2026-03-05T04:29:59Z |
5f73cae to
2818a6e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2818a6eda6
ℹ️ 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".
| return fn(store); | ||
| }, | ||
| ), | ||
| ensureSessionRuntimeCleanup: vi.fn(async () => null), |
There was a problem hiding this comment.
Allow cleanup-failure test to reach the failure path
The shared mock hard-codes ensureSessionRuntimeCleanup to always succeed, so the later test that expects reset cleanup failure (does NOT fire hook when session runtime cleanup fails) never actually exercises that branch. In this setup, mocking waitForEmbeddedPiRunEnd has no effect because sessions.reset calls the mocked ensureSessionRuntimeCleanup directly, causing the assertion that runBeforeReset is not called to fail (or, if adjusted, to provide false confidence).
Useful? React with 👍 / 👎.
2818a6e to
65fcaf3
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 65fcaf3f82
ℹ️ 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".
| return fn(store); | ||
| }, | ||
| ), | ||
| ensureSessionRuntimeCleanup: vi.fn(async () => null), |
There was a problem hiding this comment.
Let cleanup-failure test hit the real failure branch
ensureSessionRuntimeCleanup is globally mocked to always return null, so the later case that sets waitForEmbeddedPiRunEnd to false never triggers the cleanup-error path in sessions.reset. In this setup the reset flow still proceeds and can call runBeforeReset, which means the test either fails for the wrong reason or provides false confidence about failure handling. Override ensureSessionRuntimeCleanup in that test (or stop mocking it there) so the negative branch is actually exercised.
Useful? React with 👍 / 👎.
|
Closing this PR because the author has more than 10 active PRs in this repo. Please reduce the active PR queue and reopen or resubmit once it is back under the limit. You can close your own PRs to get back under the limit. |
Summary
Describe the problem and fix in 2–5 bullets:
api.on('before_reset', ...)handlers never fired when a session reset was triggered via the gatewaysessions.resetRPC (e.g., from the TUI/newor/resetcommand). The gateway path only calledtriggerInternalHook()(internal hook bus) but never invokedhookRunner.runBeforeReset()(plugin hook runner). The auto-reply path incommands-core.tscorrectly calls both.before_resetto save state or flush buffers received no signal for gateway-originated resets — the dominant reset path in production (TUI, web UI, mobile).sessions.resethandler insrc/gateway/server-methods/sessions.tsnow checkshookRunner.hasHooks("before_reset")and callshookRunner.runBeforeReset()fire-and-forget before the session is cleared, mirroring the pattern incommands-core.ts:90-130.triggerInternalHook()calls unchanged. Plugin SDK, hook runner interface, and all other RPC handlers are unmodified.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
Plugins registering
api.on('before_reset', ...)now receive hook events when a session is reset from any gateway-connected client (TUI, web UI, mobile apps). Previously this only fired on the auto-reply path. No config or API changes required.Security Impact (required)
No)No)No)No)No)Yes, explain risk + mitigation: N/ARepro + Verification
Environment
before_resetSteps
api.on('before_reset', handler)/newor/resetExpected
before_resethandler fires with session messages and reason before the session is clearedActual
hookRunner.runBeforeReset()was never called from the gateway pathEvidence
Attach at least one:
New test file
src/gateway/server-methods/sessions.before-reset-hook.test.ts(4 tests) covers: hook fires when handlers registered, does not fire when no handlers, correct reason propagation, transcript JSONL read and messages extracted.Human Verification (required)
What you personally verified (not just CI), and how:
commands-core.tsauto-reply andsessions.tsgateway) side by side. Confirmed the gateway path previously lackedhookRunner.runBeforeReset(). Verified new code follows the same guard pattern used elsewhere. Usestarget.agentId(properly resolved byresolveGatewaySessionStoreTarget) instead of naive string splitting.reason: "new"propagates correctly. Hook errors caught and logged without blocking reset.Compatibility / Migration
Yes)No)No)Failure Recovery (if this breaks)
src/gateway/server-methods/sessions.tsto remove thebefore_resethook blocksrc/gateway/server-methods/sessions.tsbefore_resethandler throwing an unhandled error that escapes the try/catch (should be swallowed by the catch + logVerbose)Risks and Mitigations
before_resethandler could hang or be slowrespond()call and session store update proceed immediately, so RPC response latency is unaffected.