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 <ulia@MacBook-Air.local>
This commit is contained in:
ulisavo 2026-08-17 17:21:00 -04:00 committed by GitHub
parent 7a4767017e
commit b90da4d115
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 1 deletions

View File

@ -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();

View File

@ -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 },