paperclip/server
Eric Brookfield 20482a4cb6
fix(server): gate heartbeat-fallback comment to never publish raw transcript (#10143)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents run on a heartbeat; when an issue-scoped run ends, the server
records the outcome on the issue's board thread.
> - Normally the agent posts its own summary comment via `POST
/comments`. When it doesn't, the server has a fallback that
auto-publishes a run summary so the board isn't left silent.
> - That fallback (`buildHeartbeatRunIssueComment` in
`server/src/services/heartbeat-run-summary.ts`) returns
`resultJson.summary` **verbatim**, with no length cap or shape check.
> - For runs that never produce a final `result`, `summary` is
concatenated **inter-tool narration** ("Let me check…", "I'll fetch…",
joined by the claude-local adapter's parser). The fallback then dumps
that raw transcript onto the public board thread.
> - In practice this produces long, confusing transcript comments that
mislead reviewers and other agents about what actually happened.
> - This PR gates the fallback so it publishes a clean summary or a
short stub, never raw transcript.
> - The benefit is that the board thread stays trustworthy: a missing
agent summary degrades to a one-line "no summary this run" note instead
of leaking internal narration.

## Linked Issues or Issue Description

No public GitHub issue exists for this; describing it here as a bug
report.

**What happened:** When an issue-scoped heartbeat run finishes without
the agent posting its own comment, the server's fallback publishes
`resultJson.summary` verbatim as the board comment. When the run
produced no final result, that value is concatenated inter-tool
narration, so raw transcript is posted to the issue thread.

**Expected behavior:** The fallback should post a concise summary when
one is available, and otherwise a short stub — never multi-hundred-line
raw narration.

**Steps to reproduce:**
1. Run an issue-scoped agent turn that ends without calling `POST
/comments` and without emitting a final `result` (only inter-tool
narration).
2. Observe the auto-published board comment: it is the full narration
transcript.

**Deployment mode:** self-hosted server
(`server/src/services/heartbeat.ts` fallback path).

**Prior attempt:** an earlier PR for this change was auto-closed when
its head branch was renamed to strip an internal ticket id from the
branch name; this PR supersedes it.

**Related PR:** #7505 (`fix(heartbeat): skip auto-mirror run-summary
comment on cross-owner wakes`) touches the same fallback area but
addresses a different case (cross-owner wakes); this PR is
complementary, gating the *content* of the fallback rather than *when*
it fires.

## What Changed

- `server/src/services/heartbeat-run-summary.ts`:
`buildHeartbeatRunIssueComment` now gates the fallback text. After
resolving `summary` → `result` → `message`, if the text opens with a
narration phrase (`let me`, `i'll`, `i need to`, `i can see`, `looking
at`, `fetching`, `checking`, `first,`) **or** exceeds
`MAX_FALLBACK_COMMENT_CHARS` (1200), it returns a fixed stub: *"Run
completed. Agent did not post a summary comment this run (transcript
withheld — see run log)."* Otherwise it returns the text unchanged.
- `server/src/__tests__/heartbeat-run-summary.test.ts`: added cases for
each narration opener, the length cap, the exact 1200-char boundary
(posts), and clean-summary passthrough.

Runs where the agent posts via the API are unaffected — the fallback
only fires when no agent comment is found for the run, and that call
site is unchanged.

## Verification

- `pnpm --filter @paperclip/server test heartbeat-run-summary` — 13/13
pass (new + existing cases).
- Manual reasoning: the gate is a pure function of the resolved text;
API-posted runs never reach it.
- **CI note:** at the time of opening, `pnpm install --frozen-lockfile`
fails on this branch's base commit with
`ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` (patchedDependencies drift). This
reproduces on every PR based on the current `master` tip (e.g. #10137)
and is unrelated to this two-file change; PRs cut from the prior master
(e.g. #10135) install cleanly. This should clear once the `Refresh
Lockfile` job lands a corrected lockfile on `master` and this branch is
rebased. Happy to rebase or fold in the lockfile fix if a maintainer
prefers.

## Risks

Low risk. The change is confined to one pure function and its tests,
touches no schema or migration, and only alters the *fallback* comment
path (never the normal API-posted path). Worst case is a legitimate
clean summary that happens to open with a gated phrase gets replaced by
the stub — the run log still holds the full detail.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`), 1M-token context window, extended
thinking, with tool use.

## 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
(`fix/gate-heartbeat-fallback-comment`) 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
- [ ] I have updated relevant documentation to reflect my changes (no
user-facing docs affected)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (blocked on a master-side
lockfile drift, see CI note)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending re-review on this PR)
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-12 16:43:13 -07:00
..
scripts fix(server): use stable tsx/cli entry point in dev-watch 2026-03-28 06:42:03 +07:00
src fix(server): gate heartbeat-fallback comment to never publish raw transcript (#10143) 2026-08-12 16:43:13 -07:00
CHANGELOG.md chore: release v0.3.1 2026-03-12 13:09:22 -05:00
package.json fix(server): export manual OpenTelemetry spans (#10565) 2026-07-31 12:47:57 -07:00
tsconfig.json Fix root TypeScript solution config 2026-03-09 14:09:30 -05:00
vitest.config.ts fix(test): stop flaky server-suite afterAll hook timeouts (#10024) 2026-07-22 11:51:30 -07:00