Repository navigation
Effect Service Conventions: 1 issue found
apps/web/src/browser/browserRecording.ts(newcaptureTabMediaStreamWithTimeout): the media-capture timeout is a pure domain condition but is expressed by manufacturing a bareErrorsolely to populatecauseof the genericBrowserRecordingOperationError({ operation: "capture-media-stream" }). The wrapper's message is derived only fromoperation/tabId, so a stalled acquisition is indistinguishable from a rejectedgetUserMedia. Convention is to model such a failure withSchema.TaggedErrorClassand structured attributes (e.g.tabId,timeoutMs, nocause) and pass the structured error through at the translation boundary.
Previously reported findings are resolved: BrowserRecordingFormatUnavailableError now models the missing MediaRecorder output format, and PreviewRecordingSourceSizeUnavailableError (added to the PreviewManagerError union) models invalid measured source dimensions in PreviewManager.startRecording.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/effect-service-conventions.md
More information about how Check Run Agents work can be found in our Help Center.
Files reviewed against the Effect service conventions:
apps/desktop/src/ipc/methods/preview.ts—startRecordingcorrectly moved off the void-onlytabMethodhelper toDesktopIpc.makeIpcMethodwith an explicitresult: DesktopPreviewRecordingSourceSchema; the handler acquiresPreviewManager.PreviewManagerfrom the environment via the module namespace. No issues.apps/desktop/src/preview/Manager.ts— newPreviewRecordingSourceSizeUnavailableErrorusesSchema.TaggedErrorClasswith structuredtabId/webContentsId, derivesmessagefrom those attributes, carries no fabricated cause (correct for a pure validation failure), and is registered in thePreviewManagerErrorunion. The newrecording.measureSource/recording.warmSource/recording.getMediaSourceIdboundaries use the existingattempt/attemptPromisehelpers with bounded context; the service interface change (startRecordingreturningDesktopPreviewRecordingSource) stays inline in theContext.Servicedeclaration.recordingFileExtensionis an exported pure helper. No issues.packages/contracts/src/ipc.ts,packages/contracts/src/settings.ts— schema/type additions only; named imports from@t3tools/contractsat consumers are expected. No issues.apps/web/src/browser/browserRecording.ts— renderer-side imperative adapter;Schemaimported from its subpath namespace. One finding, reported inline on the timeout construction.apps/web/src/components/settings/*— UI wiring for the new client setting only; no service or error modeling involved.- Test files were reviewed for shape/import conventions (
Foo["Service"], namespace imports) and no violations were found.
No ManagedRuntime.make, runPromise/runPromiseExit, Layer.succeed, or service-instance parameters were introduced in the changed scope.
🔍 Agent activity — 10 tool calls
💭 Checking the current code against the previously flagged issues, both seem resolved: Manager.ts now uses PreviewRecordingSourceSizeUnavailableError instead of a bare Error, and browserRecording.ts uses BrowserRecordingFormatUnavailableError for the missing format case. Now I want to review new potential violations. The BrowserRecordingOperationError's operations list changed slightly (cleanup added, stop-media-recorder removed), which seems fine as a multi-value discriminator. But I notice captureTabMediaStreamWithTimeout throws a bare Error for a timeout that's then wrapped as a BrowserRecordingOperationError with that as cause -- this looks like the same fabricated-Error-for-cause pattern flagged before, so I should look closer at that code and also check cleanupFailedRecordingStart's use of AggregateError with manufactured messages.
1. browse_code — apps/web/src/browser/browserRecording.ts
2. browse_code — apps/desktop/src/preview/Manager.ts
💭 With the earlier issues resolved, I'm now checking whether
captureTabMediaStreamWithTimeoutcreates a synthetic Error just to serve as acausefor the timeout condition, which would violate the rule against manufacturing errors solely to populatecause. But I notice similar patterns already exist elsewhere in the codebase —waitForRecordingStartupToSettleandrecordingStartupCancelledErrorboth use manufactured Errors this way, and the previous review already flagged and resolved a comparable instance. Since I'm meant to report only violations introduced or retained specifically within the changed scope, I need to determine whether this timeout case counts as a genuinely new instance of the pattern versus one that's carried over from prior, pre-existing code — the boundary here is genuinely ambiguous. So the timeout is really a pure domain condition without an underlying failure, which means the convention would want cause to be optional in that error class, but since cause is required there, they can't just omit it — that's more of a class design issue than something clearly introduced by this diff, so I'm inclined to let it go. I also want to check the preview.ts change where startRecording switched from the tabMethod helper to using makeIpcMethod directly with an inline handler, which seems fine since it needed the result schema. The other pieces check out too: the new structured source-size-unavailable error is properly added to the manager's error union, the exported recordingFileExtension helper is pure, the imports in browserRecording.ts look correct, and the imperative settings access there is acceptable given it's a promise-based renderer boundary rather than an Effect context. The contracts schema also looks fine. Now I want to look at the attempt/attemptPromise helper. I'm checking whetheracquireBrowserSurfaceActivityandactivityByTabIdalready exist in browserSurfaceStore, since that file isn't shown in the diff but tests reference it—I need to confirm it's pre-existing rather than a missing piece. I also want to verifyensureClientSettingsHydratedis actually exported from the settings hook module.
3. git_tools — acquireBrowserSurfaceActivity|activityByTabId
4. git_tools — ensureClientSettingsHydrated
💭 I'm checking whether the diff violates any Effect service conventions, particularly looking at the desktop IPC method change and the manager service interface change in preview.ts.
5. browse_code — apps/desktop/src/ipc/methods/preview.ts
💭 This looks consistent with existing conventions. The validation errors like PreviewRecordingSourceSizeUnavailableError correctly have no cause, and reusing PreviewWebContentsNotFoundError for webview replacement makes sense. I want to verify the new operation literals for measuring, warming, and getting the media source ID were properly added to the operation type union used by the manager's attempt functions.
6. git_tools — recording.measureSource|recording.warmSource|recording.getMediaSourceId
7. git_tools — const attemptPromise|const attempt |PreviewOperationError extends
💭 I'm noting that
captureTabMediaStreamWithTimeoutcreates a manufacturedErrorjust to populate acausefor the timeout case, repeating an anti-pattern that already existed in the prior code where the caller built a synthetic error for "no valid recording frame arrived." This is technically new code in the PR, but since the same pattern persists untouched elsewhere (waitForRecordingStartupToSettle,recordingStartupCancelledError), I'm weighing whether flagging it is consistent with the guidance against demanding unrelated cleanup, even though it's a clear textual match to the "don't manufacture an Error merely for cause" convention. I still need to decide if this should count against the change given how deeply established the pattern already is.
8. git_tools — did not settle
💭 I'm wondering whether that timeout-wrapped capture function existed back at that earlier commit, so I want to look at the diff for browserRecording.ts between then and now.
9. git_tools — d876adc..ff7e4b9
💭 The grep at commit d876adc only found one match for "startup did not settle," while HEAD has two matches including the media-capture timeout error. That suggests the capture timeout message didn't exist yet at that earlier commit—let me double check HEAD's results to confirm. Looking at the diff, the base code had a
waitForFirstFrameSizefunction with its own timeout logic that manufactured an Error for the cause, but it was removed in this PR and a similar pattern was reintroduced in a new helper. Given the guidance against manufacturing an Error just to populatecause, this looks like a legitimate finding since it's freshly introduced code, not something previously seen and left unflagged. Since the timeout condition genuinely has no underlying failure, representing it with a fabricated Error string under the shared operation tag violates the convention calling for a structured tagged error with explicit attributes like tabId and timeoutMs instead of a cause. Although there's a similar pre-existing pattern elsewhere that's untouched legacy code, this new helper falls within the changed scope the review should focus on, so I'll flag it. Now I'm also considering whether the browserRecording error class's added "cleanup" operation overlaps awkwardly with the existing "stop" operation. Since the check defaults to failure whenever findings exist, this single finding pushes the state to failure. Reconsidering its strength: is fabricating a timeout Error solely to populate acausereally a violation? The concern is that the wrapper's actual distinguishing information (timeout vs. other failure) lives only inside that manufactured Error's message rather than in a structural field, which does fit the pattern the convention warns against. I'm also thinking of a fix: mirror the existingBrowserRecordingFormatUnavailableErrorpattern by defining a dedicatedBrowserRecordingCaptureTimeoutError(tabId, timeoutMs) with no cause, thrown directly by the helper, then updating the call site so this error passes through instead of getting swallowed into the generic capture-media-stream wrapper. Looking at the wrapping try/catch logic now to see how the pass-through would need to work there. Since this requires touching both the helper and the call site's error handling, it's really a multi-hunk cross-cutting change rather than a single self-contained fix, so I'll describe the approach rather than give a unified diff. I'm now checking the exact line number in the head file where the inline comment should attach, making sure it falls within the diff's added lines around line 221.
10. modify_pr — One error-modeling convention issue in the new browser recording capture path. The two findings from earlier runs (the fabricated Error for the missing output format, and the bare Error for invalid measured source dimensions in PreviewManager.startRecording) are both resolved — BrowserRecordingFormatUnavailableError and PreviewRecordingSourceSizeUnavailableError now model those failures structurally.
Agent Credits: 111 credits
Token usage: 16 uncached input · 544,095 cache-read input · 88,947 cache-write input · 8,811 output
Agent Credits may also include non-token charges from external tools such as web research.