fix(issues): clear monitorNotes when a monitor is cleared (PEN-1995) - #1461
Merged
Conversation
`issues.monitorNotes` describes the monitor that is currently armed. Every clear path nulled `monitorNextCheckAt` and `monitorWakeRequestedAt` but left the notes column populated, so a retired monitor's text stayed on the row looking exactly like live instructions -- and, once `executionPolicy` was already null, no supported write could remove it. Observed on PEN-1995: notes registering a discriminator to read at a trigger time outlived their monitor by four days (cleared 2026-08-18T17:50:38.860Z, still present 2026-08-22 with `monitorNextCheckAt: null` throughout). The next reader had to arm a throwaway monitor purely to overwrite the column. Route all five clear sites -- invalid-for-issue-state, bounds-exhausted, convergence-stalled, monitor-removed-from-policy, and `buildIssueMonitorClearedPatch` -- through one helper that nulls the notes alongside the scheduling columns, so a future branch cannot forget one. `buildIssueMonitorTriggeredPatch` deliberately keeps the notes: a triggered monitor has no `nextCheckAt` but is pending a wake that is meant to read them. The audit copy is unaffected -- `buildClearedMonitorState` carries the notes onto `executionState.monitor.notes` -- so nulling the column is lossless, and the new manual-path test asserts both halves. Refs PEN-1995, PEN-1990 Signed-off-by: Cto <cto@paperclip.blockcast.net>
Author
1 similar comment
Author
Author
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
Author
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: db19d33
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- Centralizes the three persisted monitor-clear columns so the policy transition branches cannot clear scheduling state while leaving stale notes behind.
- Preserves the cleared monitor's notes in
executionState.monitor.notes, and the tests cover both the database-column cleanup and audit-state retention. - Keeps triggered-monitor notes intact, which avoids conflating a triggered wake with a fully cleared monitor.
Recommended Action
- No Critical or Important issues found. This formal review is COMMENTED because the PR is authored by the Ally App.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thinking Path
Linked Issues or Issue Description
Refs PEN-1995, PEN-1990 (Paperclip-tracked; no GitHub issue).
Duplicate / related PR search (
monitorNotes,monitor clear notes, open + closed): no duplicate exists — no other PR nulls the notes column on clear. Related, non-overlapping:server/src/__tests__/lapsed-monitor-grace-bound.test.tsand recovery; no overlap withissue-execution-policy.ts. Open.heartbeat.ts), no shared files. Open.Bug. What happened: after a monitor is cleared,
issues.monitorNotesretains the retired monitor's text indefinitely. BecausemonitorNextCheckAtisnull, nothing will ever fire to act on it, but the notes remain the row's most authoritative-looking statement of what to do next — and there is no supported way to clear them.applyMonitorTransitiononly ever writes the column when a monitor is armed (patch.monitorNotes = input.policy.monitor.notes ?? null); all four clear branches, plusbuildIssueMonitorClearedPatch, omit it. Sending{"executionPolicy": {}}normalizes tonull, which is a no-op when the policy is alreadynull, so the stale text survives every documented clear.Expected: clearing a monitor clears the notes that described it.
Workaround this replaces: arm a throwaway monitor with corrected notes purely to overwrite the column, then clear it again — two writes and a transient armed monitor to fix a text field.
What Changed
server/src/services/issue-execution-policy.ts: addedclearArmedMonitorColumns(patch), which nullsmonitorNextCheckAt,monitorWakeRequestedAt, andmonitorNotestogether.applyMonitorTransitionthrough it: invalid-for-issue-state, bounds-exhausted, convergence-stalled (BLO-18294), and monitor-removed-from-policy.buildIssueMonitorClearedPatchnow returnsmonitorNotes: nullfor the same reason.server/src/__tests__/issue-execution-policy.test.ts: notes nulled on the done/auto-clear path (assertion added to the existing test), on the manual monitor-removed path, and on the invalid-issue-state path. The manual-path test also asserts the audit copy is still onexecutionState.monitor.notes.No schema change, no API-shape change:
monitorNoteswas already nullable and already returned on reads.Verification
Fail-first was checked, not assumed: with the source change reverted and the tests kept, the three new
expect(result.patch.monitorNotes).toBeNull()assertions fail (the key is absent from the patch, so it readsundefined). With the change applied, the file passes.Lossless-ness is the load-bearing claim, so it is asserted rather than argued:
buildClearedMonitorStatealready carriesnotes: input.previous?.notes ?? nullonto the clearedexecutionState.monitor, andderivePersistedMonitorStatefalls back tofromState?.notes. The manual-path test asserts both halves — columnnull, state copy intact — so a future change that drops the audit copy fails here.Risks
Low. Behavior change is confined to the value of one nullable text column at monitor-clear time.
issues.monitorNotesto recover a retired monitor's text will now getnulland must readexecutionState.monitor.notes. I grepped the writers (heartbeat.ts,issues.ts,issue-execution-policy.ts,execution-workspaces.ts,routes/issues.ts); the derivation path already prefers the state copy, and no consumer reads the column expecting post-clear content.buildIssueMonitorTriggeredPatchkeepsmonitorNotespopulated. A triggered monitor hasnextCheckAt: nullbut is pending a wake that is supposed to read those notes — nulling there would break the mechanism this PR exists to protect.Model Used
Anthropic Claude, model id
claude-opus-5[1m]as reported by the harness (Opus tier, 1M-token context), agentic tool use with repository read/write and test execution. Change authored and verified by the model; the diagnosis originated from measured productionheartbeat_runsdata on PEN-1995.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template — (b), bug_report shape aboveissue-execution-policy.test.ts, 45 across adjacent monitor suites,tsc --noEmitcleanreviewgate was the only red; re-run after adding this checklistRepo-specific gates (retained from the prior checklist)
doc/SPEC-implementation.md(no spec'd behavior changed; this restores the stated meaning of an existing field)