Skip to content

Suppress long_active_duration productivity review for deliberate future monitors - #33

Merged
kkroo merged 1 commit into
masterfrom
release-eng/blo-5505-suppress-long-active
May 17, 2026
Merged

Suppress long_active_duration productivity review for deliberate future monitors#33
kkroo merged 1 commit into
masterfrom
release-eng/blo-5505-suppress-long-active

Conversation

@blockcast-ci-packages

Copy link
Copy Markdown

Thinking Path

  • Paperclip orchestrates AI agents for zero-human companies
  • The productivity-review evaluator watches assigned issues for stalled / churning execution patterns and files a Review productivity issue when a trigger fires
  • The long_active_duration trigger keeps firing on issues that are intentionally long-running because the assignee scheduled a future monitorNextCheckAt (watcher / wake-on-condition pattern); BLO-5383 produced two false-positive reviews in <24h (BLO-5444 → BLO-5500), with ~50 more queued before the monitor wakes on 2026-05-29
  • Each false review costs CEO triage + an extra heartbeat run on the assignee
  • This pull request maps BLO-5505's manager/CEO wording onto the platform's persisted scheduler contract — suppressing long_active_duration only when monitorNextCheckAt > now() AND monitorScheduledBy ∈ {assignee, board} (the only two normalized values today)
  • The benefit is no more false-positive long-active reviews on deliberate watchers, with no_comment_streak and high_churn still firing on genuinely stuck work

What Changed

  • server/src/services/productivity-review.ts: adds MonitorScheduledSuppression evidence shape, deliberateFutureMonitor() guard, recordMonitorScheduledSuppression() (logs issue.productivity_review_suppressed activity row), and closeOpenSuppressedMonitorReviews() (closes any currently open long_active_duration review whose source has a deliberate future monitor — backfill path so we don't have to triage the queued BLO-550x batch by hand). reconcileProductivityReviews calls the closer at the top of each cycle and short-circuits the create path when evidence is a suppression. isProductivityReviewContinuationHoldActive is type-narrowed so the suppression shape never enters the hold path.
  • server/src/__tests__/productivity-review-service.test.ts: extends seedAssignedIssue() with monitorNextCheckAt / monitorScheduledBy opts, adds 5 new vitest cases (suppress, expired-monitor still fires, no-comment not suppressed, close-open long-active, do-not-close open no-comment), and adjusts the existing high-churn test to assert monitorScheduledSuppressed: 0 when the source has a future monitor.

Reviewer-confirmed scope (BLO-5509 description): assignee and board are the only normalized deliberate scheduler values persisted today. If distinct manager / ceo scheduler values are desired, that is a separate schema/API change and intentionally out of scope.

Verification

  • pnpm exec vitest run server/src/__tests__/productivity-review-service.test.ts → 16/16 passed (28.6s)
  • pnpm --filter @paperclipai/server typecheck → 0 errors

Risks

Low risk. The suppression heuristic is gated narrowly:

  • Only long_active_duration is affected — no_comment_streak, high_churn, etc. continue to fire on the same source issue unchanged.
  • Only fires when both monitorNextCheckAt > now() AND monitorScheduledBy ∈ {assignee, board} (auto-monitor recovery rows are not assignee/board scheduled, so they don't trigger suppression).
  • Backfill close path only acts on reviews whose latest activity-log trigger is long_active_duration; if the trigger has switched (e.g., review was updated to a no_comment_streak), it stays open.

Model Used

  • Claude Opus 4.7 (1M context), tool-use mode, executed via Paperclip's Release Engineer agent runtime.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • If this change affects the UI, I have included before/after screenshots — N/A (server-only)
  • I have updated relevant documentation to reflect my changes — N/A (internal evaluator heuristic; activity-log row is the only operator-visible surface)
  • I have considered and documented any risks above
  • I will address all Greptile and reviewer comments before requesting merge

Refs BLO-5505, BLO-5509.

…re monitors

Adds MonitorScheduledSuppression handling to productivity-review service so a
`long_active_duration` review is not created (and any open one is closed) when
the source issue has a future `monitorNextCheckAt` set deliberately by the
`assignee` or `board` scheduler. `no_comment_streak` and `high_churn` triggers
remain active. Records an activityLog row when a long-active review is suppressed
or closed by this rule.

The issue's manager/CEO wording is mapped onto the platform's existing
normalized scheduler values; introducing distinct `manager`/`ceo` scheduler
values is a separate schema/API change and intentionally out of scope.

Refs BLO-5505, BLO-5509.
kkroo added a commit that referenced this pull request May 16, 2026
* fix(test): v513 fallout — regex, positional arg, hookTimeout

Three uncorrelated v513 merge fallouts, all blocking master's verify_canary
and PR verify on Blockcast/paperclip:

1. issues-identifier-provider: Drizzle now wraps PG errors in
   `Failed query: ...` text that no longer surfaces the constraint name
   in the matchable message. Match on the column name instead — the
   wrapped SQL still includes `identifier_provider`, which is a stable
   signal the DB rejected the unknown value.

2. plugin-orchestration-apis (2 sites): `buildHostServices` grew a 6th
   positional `lifecycleManager` argument between the merge bases, so
   `{manifest:...}` was landing in the lifecycleManager slot and
   `options.manifest` was undefined. Add an explicit `undefined` for
   lifecycleManager so the manifest reaches position 7.

3. backup-lib: same `Hook timed out in 10000ms` shape as `5f6f9c04`
   bumped for `client.test.ts` — embedded-postgres `afterEach` cleanup
   can exceed the 10s default under CI runner load. Bump to 60s to
   match the surrounding test-level timeout.

Refs paperclip-v513-merge-fallout-pattern.md. The github-webhook +
environment-runtime fallouts have deeper causes (test isolation
leak from background heartbeat execution, secret-binding gaps,
spy-call drift) and are tracked for separate cleanup commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test): github-webhook beforeEach — force-finalize stuck heartbeat runs

The wake-driving test (`drives a wake on check_run.completed ...`) calls
`heartbeat.wakeup(...)` which fires off a real `void executeRun(...)`
fire-and-forget background execution against the embedded postgres. That
execution can outlive the test it was spawned in — when the next test's
beforeEach runs, the queued/running row is still in-flight, holding row
locks that block the FK-cascade through `TRUNCATE companies CASCADE`,
and the 100-iter × 50ms drain loop hits its 10s hookTimeout (max 5s of
polling + DB query time piles up beyond the default budget).

Two-part fix:
- Switch the drain to a wall-clock-deadline (30s) so the loop genuinely
  uses the available time budget, not just 100 iterations × 50ms.
- After drain (whether converged or not), force-finalize any remaining
  queued/running rows to `failed` before TRUNCATE so the cascade can't
  block on in-flight executeRun row locks.
- Bump hookTimeout to 60s to absorb the worst case under CI load.

Closes the 2 remaining github-webhook failures from CI verify on PR #33
and the same 2 reproducing on devbox.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(env-runtime): v513 fallout — restore paperclipApiUrl + seed bindings + 4-arg spy assertions

Three correlated v513 merge fallouts in environment-runtime tests + service:

1. Restore `paperclipApiUrl` to SSH lease metadata. The v513 merge
   (f69bb69) "recover upstream changes for 13 medium-complexity files"
   dropped this single line from the lease metadata in
   server/src/services/environment-runtime.ts. The cache-lookup at the
   top of acquireRunLease still READS `lease.metadata.paperclipApiUrl`
   for the last-known-good probe optimization (BLO-1489), so the read
   side was harmless but the cache could never populate. Three SSH tests
   exposed this — restore the write.

2. Seed `company_secret_bindings` rows in both `seedEnvironment` helpers
   (environment-runtime.test.ts + environment-runtime-driver-contract).
   v513 introduced the bindings table as a required gate on secret_ref
   resolution; without a binding row, `secretService.resolve` throws
   "Secret is not bound to environment:<id> at <configPath>". Affects
   the privateKey-converted SSH tests and the apiKey secret-ref sandbox
   test (separate binding insert in the test body for apiKey since the
   secret is created outside seedEnvironment).

3. Update spy assertions for v513's added 4th positional argument
   (RPC timeout in ms) on `pluginWorkerManager.call(...)` for sandbox
   provider paths AND for `executePluginEnvironmentCommand`. Adds
   `expect.any(Number)` as 4th arg on five assertions across three
   sandbox tests + one plugin-driver test. Plugin-driver
   acquire/release/resume/destroy paths use 3 args (no timeout) but
   now pass `issueId` in the call object — adds `issueId: null` to
   four spy assertions.

All 20 tests across both files pass after these fixes.

Refs paperclip-v513-merge-fallout-pattern.md, BLO-1489.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kkroo
kkroo merged commit 1138f65 into master May 17, 2026
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants