From b90da4d1158c7b2ed695032bdd59fb59f2959742 Mon Sep 17 00:00:00 2001 From: ulisavo Date: Mon, 17 Aug 2026 17:21:00 -0400 Subject: [PATCH] fix: keep task sessions across issue comments (#10111) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Local session adapters persist task sessions so later wakes can resume the same conversation > - Session reuse correctly resets when effective execution configuration changes > - The workspace fingerprint currently includes the issue row's `updatedAt` timestamp > - Adding a comment advances that timestamp even though workspace configuration is unchanged > - The next same-issue wake therefore discards a valid task session and starts cold > - This pull request excludes that volatile timestamp while retaining actual workspace settings in the fingerprint > - The benefit is reliable same-task continuation without weakening configuration-freshness safety ## Linked Issues or Issue Description No public issue exists. The inline report below follows the bug report template. ### Pre-submission checklist - [x] I searched existing open and closed issues and found no duplicate. - [x] I reproduced the bug on the latest release and current `master`. - [x] I confirmed the error originates in Paperclip core fingerprinting, not an adapter, provider, or local configuration. ### What happened? On Paperclip 2026.720.0 and current `master`, a comment on an issue changes `issues.updated_at`. Heartbeat session fingerprinting includes that value under `workspaceConfig.issueConfigRevisionAt`, so the next wake for the same issue reports a workspace-config change and refuses the saved task session. ### Expected behavior Comment-only and other non-configuration issue updates should be delivered as wake deltas without invalidating the task session. Changes to the execution mode, issue workspace settings, project policy, environment, instructions, model, secrets, or other effective run configuration must still reset it. ### Steps to reproduce 1. Complete a local session-adapter run for an issue and retain its task session. 2. Add a comment to the issue without changing execution configuration. 3. Wake the same agent for the same issue. 4. Observe `changedCategories: ["workspaceConfig"]` and a fresh session. ### Paperclip version or commit Reproduced on Paperclip 2026.720.0 and current `master`. ### Deployment mode Self-hosted server. ### Installation method npm global install; also reproduced from the current source tree. ### Agent adapter(s) involved Codex exposed the symptom. The bug is in core fingerprint construction and is not adapter-specific. ### Database mode External Postgres. The bug is not database-specific. ### Access context Board comments trigger the timestamp change; the subsequent agent wake exposes the reset. ### Node.js version Node.js 22. ### Operating system Ubuntu 24.04. ### Relevant logs or output The next run records `changedCategories: ["workspaceConfig"]` and starts a fresh session after a comment-only mutation. ### Relevant config No unusual configuration is required. ### Additional context The regression test exercises the fingerprint directly on current `master`. ### Privacy checklist - [x] I reviewed the report for PII, credentials, private paths, company names, and instance-local identifiers. ## What Changed - Copy and sanitize the session workspace-fingerprint input before hashing. - Exclude only `issueConfigRevisionAt`, which reflects general issue mutation rather than workspace configuration. - Add regression coverage proving comment timestamps preserve the session while real workspace mode/settings changes still reset it. ## Verification - `pnpm exec vitest run server/src/__tests__/heartbeat-workspace-session.test.ts` - 120 tests passed. - `pnpm --filter @paperclipai/server typecheck` - passed. - `git diff --check` - passed. ## Risks Low risk. A general issue update no longer rotates the adapter session solely because its row timestamp changed. The fingerprint still includes issue workspace settings, issue adapter overrides, project workspace policy, environment, instructions, runtime skills, secrets, model profile, adapter configuration, and agent runtime configuration, so actual execution-config drift continues to reset. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected - check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, `gpt-5`, context-window size not exposed, reasoning and tool use enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Uliana Savostenko --- .../heartbeat-workspace-session.test.ts | 51 +++++++++++++++++++ server/src/services/heartbeat.ts | 8 ++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/server/src/__tests__/heartbeat-workspace-session.test.ts b/server/src/__tests__/heartbeat-workspace-session.test.ts index a969382862..0495f3bd93 100644 --- a/server/src/__tests__/heartbeat-workspace-session.test.ts +++ b/server/src/__tests__/heartbeat-workspace-session.test.ts @@ -2092,6 +2092,57 @@ describe("effective run session config freshness", () => { expect(decision.reasons.join("\n")).toContain("adapter config"); }); + it("does not reset for issue comment timestamps but still resets for workspace settings", async () => { + const base = await buildSessionConfigMetadata({ + workspaceConfig: { + requestedMode: "agent_default", + effectiveMode: "agent_default", + issueConfigRevisionAt: "2026-06-01T00:00:00.000Z", + issueSettings: null, + }, + }); + const commentOnly = await buildSessionConfigMetadata({ + workspaceConfig: { + requestedMode: "agent_default", + effectiveMode: "agent_default", + issueConfigRevisionAt: "2026-06-01T00:05:00.000Z", + issueSettings: null, + }, + }); + const workspaceChanged = await buildSessionConfigMetadata({ + workspaceConfig: { + requestedMode: "isolated_workspace", + effectiveMode: "isolated_workspace", + issueConfigRevisionAt: "2026-06-01T00:05:00.000Z", + issueSettings: { mode: "isolated_workspace" }, + }, + }); + + expect( + resolveTaskSessionConfigFreshness({ + hasTaskSession: true, + configuredModel: "gpt-5.4-mini", + taskSessionParams: sessionParamsWithConfigMetadata(base), + configMetadata: commentOnly, + }), + ).toMatchObject({ + reset: false, + changedCategories: [], + reasons: [], + }); + expect( + resolveTaskSessionConfigFreshness({ + hasTaskSession: true, + configuredModel: "gpt-5.4-mini", + taskSessionParams: sessionParamsWithConfigMetadata(base), + configMetadata: workspaceChanged, + }), + ).toMatchObject({ + reset: true, + changedCategories: ["workspaceConfig"], + }); + }); + it("keeps model-only compatibility as an additional reset reason", async () => { const base = await buildSessionConfigMetadata(); diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 5b3ceae2a7..b92f81358b 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -4842,6 +4842,12 @@ function buildSessionConfigCategoryValues(input: { agentConfigRevision: unknown; }) { const sanitizedSecretManifest = sanitizeSecretManifestForConfigFingerprint(input.secretManifest); + const workspaceConfig = { ...parseObject(input.workspaceConfig) }; + // issues.updatedAt also advances for comments and status changes. Those are + // wake deltas, not execution-workspace configuration changes, so including + // the timestamp here makes every comment invalidate an otherwise reusable + // task session. + delete workspaceConfig.issueConfigRevisionAt; return { adapter: { adapterType: input.adapterType, @@ -4852,7 +4858,7 @@ function buildSessionConfigCategoryValues(input: { modelProfile: input.modelProfile, instructions: input.instructions, issueOverrides: input.issueOverrides, - workspaceConfig: input.workspaceConfig, + workspaceConfig, environment: input.environment, envBindings: { environment: { env: input.environmentEnv },