Add thread environment status snapshots - #1017
Conversation
Resolves 530-commit drift against main; the stale 0048 migration is dropped here and regenerated as 0085 in a follow-up commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- workspace.pull_request RPC result is now a discriminated union (available/absent/unavailable); map absent to an available snapshot with no PR and unavailable into the retryable error path - pass hub through ThreadRuntimeDisplayDeps in restored tests - add environmentStatusSummary to thread fixtures added since the PR - drop snapshot tables in migrate rewind tests that replay 0085 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🚨 SLOP COP 🚨 · I am SlopCop. I am reviewing this pull request for security, code quality, performance, architecture, and end-to-end behavior. The stack contains only #1017. I did not skip any pull requests. I will post a comment after each phase. |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| environmentIds, | ||
| now, | ||
| }); | ||
| markEnvironmentStatusSnapshotsDue(deps.db, { |
There was a problem hiding this comment.
🚨 slopcop/review — Repeated thread-list reads force every snapshot due.
This list path sets nextRefreshAt to now for every visible environment.
The snapshot notification invalidates the same list query. An open sidebar can repeat workspace.status and gh pr view calls.
Keep the ensure call, but remove this unconditional dirty mark.
| ): EnvironmentGitStatusSnapshotRow[] { | ||
| return db | ||
| .select() | ||
| .from(environmentGitStatusSnapshots) |
There was a problem hiding this comment.
🚨 slopcop/review — Snapshot rows remain in the refresh queue after their last live thread disappears.
These due queries select only by time. No code deletes rows when an environment becomes untracked.
Git and pull request RPCs can continue forever and can delay live environments.
Filter by live tracked threads, or delete untracked snapshot rows.
|
🚨 SLOP COP 🚨 · The performance review found two high-impact issues. Thread-list reads force all visible environments due and can create a refresh loop. Snapshot rows remain due after their last live thread disappears. I posted both findings inline. |
|
🚨 SLOP COP 🚨 · The security review found one medium denial-of-service risk. An untrusted web page can call the unauthenticated thread list. Each call makes all visible snapshots due. This issue has the same cause as the first performance finding. I found no other security issues. |
| "idx": 85, | ||
| "version": "6", | ||
| "when": 1785882543282, | ||
| "tag": "0085_environment_status_snapshots", |
There was a problem hiding this comment.
🚨 slopcop/review — The journal names a migration file that this branch does not contain.
Every migrate(db) call throws because 0085_environment_status_snapshots.sql is absent. The server cannot start.
Regenerate the migration and commit the SQL file.
There was a problem hiding this comment.
🚨 slopcop/review — Fixed in 4a0facc.
The current head now includes 0085_environment_status_snapshots.sql. I checked the pushed delta.
This finding no longer applies.
| // depth cap. | ||
| export const threadResponseSchema = threadWithRuntimeSchema.extend({ | ||
| canSpawnChild: z.boolean(), | ||
| environmentStatusSummary: threadEnvironmentStatusSummarySchema, |
There was a problem hiding this comment.
🚨 slopcop/review — This required field breaks the plugin SDK test fixture.
makeThreadResponse spreads Partial<ThreadResponse> without a default for this field. The focused Turbo typecheck fails with TS2322.
Add a deterministic environmentStatusSummary before the overrides.
There was a problem hiding this comment.
🚨 slopcop/review — Fixed in 4a0facc.
The current head now gives makeThreadResponse a deterministic status summary. I checked the pushed delta.
This finding no longer applies.
| "environment-status-summary-changed": { | ||
| flush: "debounced", | ||
| dirty: [ | ||
| dirtyThreadListQueries, // Sidebar/list rows render environment git and PR signals. |
There was a problem hiding this comment.
🚨 slopcop/review — The existing sidebar pull-request hook does not read this invalidated query.
useSidebarThreadPullRequest still reads useEnvironmentPullRequest. This pull request removes that query's five-second polling.
Pending checks and mergeability can stay stale until focus.
Read the list snapshot, or invalidate the environment pull-request query.
|
🚨 SLOP COP 🚨 · The code-quality review found three direct failures or regressions. The SQL migration is absent. The plugin SDK fixture does not compile. The sidebar pull-request hook can stay stale. I posted all three findings inline. The Git and pull-request storage paths also contain much duplicate code. A shared internal helper could reduce repetition without merging their tables. |
|
🚨 SLOP COP 🚨 · End-to-end update: the current head The initial head failed because the SQL migration was absent. The author added that file, so I ran the test again. The server health route returned The browser loaded the bb home view with no console errors. The isolated data had no threads, so I could not exercise a populated snapshot view. |
SawyerHood
left a comment
There was a problem hiding this comment.
🚨 SLOP COP 🚨 · review
Review result: three findings remain on current head 4a0facc.
- Thread-list reads mark every visible snapshot due. This can create a refresh loop and unauthenticated resource exhaustion.
- Snapshot rows remain queued after their last live thread disappears.
- The sidebar pull-request hook can stay stale after this pull request removes polling.
The author fixed the missing SQL migration and SDK fixture during review. I replied to both resolved threads.
Current CI is green. The development server, health route, thread-list route, and browser smoke test passed.
The isolated data had no threads, so I could not test a populated snapshot view.
I did not approve or request changes.
…idebar PRs from snapshots Review findings from the restored PR, verified adversarially: - Demand marks (thread-list reads, thread attach) now carry a 30s staleness floor and only ever pull schedules earlier, killing the refresh -> notify -> list refetch -> mark-due loop that re-polled git and GitHub every sweep tick - Due listings skip environments with no live thread, so abandoned environments stop polling gh forever; absent-PR cadence drops to 5min - Local work-status changes no longer mark the PR snapshot due (only git-refs and lifecycle changes can alter a PR) - Snapshot writers preserve due-marks that arrive while an RPC is in flight - The due sweep groups refreshes by host and runs hosts concurrently - useSidebarThreadPullRequest reads the pushed environmentStatusSummary from the thread list entry instead of the removed client-side polling query; the whole client PR query surface (hook, key, invalidations) is deleted - Snapshot status columns are enum-typed end to end; the defensive normalizeSnapshotStatus re-parse is gone - Restore main's 10s sweep interval; dedupe change-kind sets via THREAD_CHANGE_KINDS_AFFECTING_TRACKED_ENVIRONMENTS in @bb/db; drop dead singular mark-due helpers, pass-through wrappers, and the startup alias; optimistic thread inserts seed pending (not not_applicable) summaries Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… once Adversarial verification of the previous commit found the in-flight-mark preservation unreachable on the sweep path: a due row's stored schedule never changed under the only-pull-earlier guard, so the writer's expectedNextRefreshAt comparison always matched and the computed backstop overwrote the mark. Change-driven (floor 0) marks now restamp the row at the mark time, making the divergence observable; demand marks still leave due rows alone. Due listings also exempt never-refreshed rows from the live-thread gate so a thread archived before its environment's first refresh cannot render a pending signal forever. Package-level db tests cover the floor, the in-flight race, and the gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resurrects #291 against current main (the original branch had drifted 530 commits behind and its migration chain was stale), then hardens the restored scheduler based on an adversarial review pass.
What this adds
environment_git_status_snapshots,environment_pull_request_status_snapshots, migration 0085) and refreshed by the periodic sweepenvironmentStatusSummary(git + PR signals) projected into thread responses and thread list entries; a newenvironment-status-summary-changedthread change kind pushes updates to clientsexperimental_useSidebarThreadPullRequest) now read the pushed snapshot; all client-side PR polling (hook, query key, invalidations, refetch interval) is deletedRefresh model
work-status-changedrefreshes git status;git-refs-changedand lifecycle changes also refresh PR state. Local edits never re-poll GitHub.refreshedAt + 30s— a list read of fresh data is a no-op, so push-notify → refetch → mark-due cannot loop.Changes vs #291
workspace.pull_requestRPC result union (available/absent/unavailable)Note: the built-in sidebar (ThreadRow) does not render these signals yet; consumers today are ThreadDetailView and plugin sidebars. No
HOST_DAEMON_PROTOCOL_VERSIONbump: wire shapes are unchanged, only which watch targets are included.Validation
pnpm exec turbo run build typecheck lint test --force(full workspace, uncached)pnpm exec turbo run test --filter=@bb/integration-testsCloses #291.
🤖 Generated with Claude Code