fix(heartbeat): clear sessionFile on new isolated session to prevent context accumulation#57136
Conversation
Greptile SummaryThis PR fixes a bug where The fix is a single targeted change: when
Confidence Score: 5/5Safe to merge — single-line targeted fix with correct semantics and no risk of regressions on the non-isolated session path. The fix is minimal, well-reasoned, and directly addresses the root cause. The No files require special attention.
|
| Filename | Overview |
|---|---|
| src/cron/isolated-agent/session.ts | Adds ...(isNewSession && { sessionFile: undefined }) to clear the inherited session file path when a new session is created, fixing unbounded context growth with isolatedSession: true. |
Comments Outside Diff (1)
-
src/cron/isolated-agent/session.test.ts, line 126-144 (link)Missing regression test for
sessionFileclearingThe existing
forceNewand stale-session tests verify thatsessionIdrotates and that user-configured overrides (e.g.modelOverride) are preserved, but none of them assert thatsessionFileis set toundefinedon the new entry — which is exactly the behaviour this PR adds. Without such an assertion, a future refactor of the spread order could silently reintroduce the same bug.Consider adding an explicit check in the
forceNewtest (and/or the stale-session test):it("creates new sessionId when forceNew is true", () => { const result = resolveWithStoredEntry({ entry: { sessionId: "existing-session-id-456", updatedAt: NOW_MS - 1000, systemSent: true, modelOverride: "sonnet-4", providerOverride: "anthropic", sessionFile: "old-uuid.jsonl", // ← add a stored sessionFile }, fresh: true, forceNew: true, }); expect(result.sessionEntry.sessionId).not.toBe("existing-session-id-456"); expect(result.isNewSession).toBe(true); expect(result.sessionEntry.modelOverride).toBe("sonnet-4"); // Core regression guard for issue #43767 expect(result.sessionEntry.sessionFile).toBeUndefined(); });
The same assertion would be valuable in the "creates new sessionId when session is stale" test.
Prompt To Fix With AI
This is a comment left during a code review. Path: src/cron/isolated-agent/session.test.ts Line: 126-144 Comment: **Missing regression test for `sessionFile` clearing** The existing `forceNew` and stale-session tests verify that `sessionId` rotates and that user-configured overrides (e.g. `modelOverride`) are preserved, but none of them assert that `sessionFile` is set to `undefined` on the new entry — which is exactly the behaviour this PR adds. Without such an assertion, a future refactor of the spread order could silently reintroduce the same bug. Consider adding an explicit check in the `forceNew` test (and/or the stale-session test): ```ts it("creates new sessionId when forceNew is true", () => { const result = resolveWithStoredEntry({ entry: { sessionId: "existing-session-id-456", updatedAt: NOW_MS - 1000, systemSent: true, modelOverride: "sonnet-4", providerOverride: "anthropic", sessionFile: "old-uuid.jsonl", // ← add a stored sessionFile }, fresh: true, forceNew: true, }); expect(result.sessionEntry.sessionId).not.toBe("existing-session-id-456"); expect(result.isNewSession).toBe(true); expect(result.sessionEntry.modelOverride).toBe("sonnet-4"); // Core regression guard for issue #43767 expect(result.sessionEntry.sessionFile).toBeUndefined(); }); ``` The same assertion would be valuable in the "creates new sessionId when session is stale" test. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/cron/isolated-agent/session.test.ts
Line: 126-144
Comment:
**Missing regression test for `sessionFile` clearing**
The existing `forceNew` and stale-session tests verify that `sessionId` rotates and that user-configured overrides (e.g. `modelOverride`) are preserved, but none of them assert that `sessionFile` is set to `undefined` on the new entry — which is exactly the behaviour this PR adds. Without such an assertion, a future refactor of the spread order could silently reintroduce the same bug.
Consider adding an explicit check in the `forceNew` test (and/or the stale-session test):
```ts
it("creates new sessionId when forceNew is true", () => {
const result = resolveWithStoredEntry({
entry: {
sessionId: "existing-session-id-456",
updatedAt: NOW_MS - 1000,
systemSent: true,
modelOverride: "sonnet-4",
providerOverride: "anthropic",
sessionFile: "old-uuid.jsonl", // ← add a stored sessionFile
},
fresh: true,
forceNew: true,
});
expect(result.sessionEntry.sessionId).not.toBe("existing-session-id-456");
expect(result.isNewSession).toBe(true);
expect(result.sessionEntry.modelOverride).toBe("sonnet-4");
// Core regression guard for issue #43767
expect(result.sessionEntry.sessionFile).toBeUndefined();
});
```
The same assertion would be valuable in the "creates new sessionId when session is stale" test.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(heartbeat): clear sessionFile on new..." | Re-trigger Greptile
15704d8 to
d80337e
Compare
8c3c201 to
544834d
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 544834d95e
ℹ️ 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".
| lastAccountId: undefined, | ||
| lastThreadId: undefined, | ||
| deliveryContext: undefined, | ||
| sessionFile: undefined, |
There was a problem hiding this comment.
Archive old transcript before clearing sessionFile
Clearing sessionFile on every new cron/heartbeat session causes each isolated run to switch to a fresh <sessionId>.jsonl, but the previous transcript path is dropped from the only retained session entry and never archived/deleted in the resolveCronSession call paths (run.ts/heartbeat-runner.ts just overwrite the same key). In environments using isolatedSession: true, this creates an ever-growing set of orphan transcript files and can eventually consume significant disk space; this needs a rollover cleanup step (or explicit archival) before nulling the field.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
While the point makes sense, I feel orphan clearances are a separate issue which is out of scope for this fix.
544834d to
0df8ec2
Compare
…context accumulation When isolatedSession: true is configured, resolveCronSession generates a new sessionId (UUID) on every heartbeat tick. However the new session entry was spread from the prior entry, preserving the old sessionFile path. resolveSessionFilePath prefers entry.sessionFile when set, so every heartbeat continued appending to the same old transcript file despite a new sessionId. This caused unbounded context growth (18K → 190K+ tokens over 24h). Fix: clear sessionFile when isNewSession is true, analogous to the existing cleanup of lastChannel, lastTo, deliveryContext, etc. Fixes openclaw#43767
0df8ec2 to
0c2c5a8
Compare
Problem
When
isolatedSession: trueis configured for heartbeats,resolveCronSessionis called withforceNew: trueand correctly generates a newsessionId(UUID) on every heartbeat tick.However, the new session entry was spread from the prior entry (
...entry), which preserved the oldsessionFilepath from the previous run.resolveSessionFilePathhas this logic:So every heartbeat continued appending to the same old transcript file, even though a new
sessionIdwas assigned. This caused unbounded context growth that eventually triggered model fallbacks and failed heartbeat runs — despiteisolatedSession: truebeing set.Observed: context grew from ~18K tokens to 190K+ tokens over 24 hours, causing repeated Gemini API errors and fallback to backup models.
Fix
Clear
sessionFilewhenisNewSessionis true, soresolveSessionFilePathfalls through to derive a fresh path from the newsessionId.This is analogous to the existing cleanup of
lastChannel,lastTo,deliveryContext, etc. that was already done on new session rollover.Testing
isolatedSession: true+ 1h heartbeat interval: each heartbeat now creates a new<uuid>.jsonltranscript file, starting at ~18K tokens instead of inheriting prior session history.Related
Fixes #43767