refactor(effect): trace shared and source-control operations#21990

merged4
effect-fn/conventions
effect-fn/shared
opened9d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 8d ago

Problem

Reusable shared and source-control operations still wrap Effect.gen, leaving operation tracing inconsistent and allowing the earlier Effect function migration to regress. They should expose named operation spans while preserving existing transport annotations and avoiding spans on repeated cache lookups.

Change

Replace 52 reusable generator wrappers in shared utilities and source-control providers with Effect functions. Preserve existing span names and options, keep policy and settlement annotations on the existing transport span, and leave repeated directory-cache lookups untraced.

Tracing choices

  • Named Effect.fn covers reusable filesystem, process, host, and source-control operations, preserving existing span names and options.
  • Effect.fnUntraced keeps repeated directory-cache lookups (withPathDirectoryListings) from creating a span per lookup. GitHubRequestPolicy.request and its settlement helper also stay untraced so quota, status, and request annotations remain on the existing REST or GraphQL transport span.

Scope and approval

This is a maintainer-requested layer of the Effect function migration, authored by juliusmarminge (maintainer list). The maintainer explicitly requested the migration and this PR stack. This layer changes tracing boundaries in existing shared utilities and source-control operations while preserving their operational behavior; it adds no product feature or default. There is no separate public approval discussion.

Depends on #21989.

Verification

Scoped typechecks passed for shared, Tailscale, and the six affected source-control packages. Fourteen focused suites passed: 387 tests, one skipped, including the REST and GraphQL tracing assertions.

Nonvisual refactor; evidence comes from the focused behavior and tracing tests. No UI verification applies, and no additional tests were run for this description-only update.

Diff with whitespace ignored

ViewAdded linesRemoved lines
Normal diff1,1501,120
Whitespace ignored (git diff -w)208178

Review the files with whitespace hidden. These counts and the patch below are a snapshot of 5b256b8, compared with this PR's merge base. Whitespace-only changes are ignored; line wrapping and other textual changes still count.

Expand the full whitespace-ignored patch (20 files; unchanged context omitted)
diff
1diff --git a/packages/shared/src/DrainableWorker.ts b/packages/shared/src/DrainableWorker.ts
2index 4515e5ca62..6f994068a5 100644
3--- a/packages/shared/src/DrainableWorker.ts
4+++ b/packages/shared/src/DrainableWorker.ts
5@@ -18 +18,5 @@ export interface DrainableWorker<A> {
6-export const makeDrainableWorker = <A, E, R>(
7+export const makeDrainableWorker = Effect.fn("DrainableWorker.makeDrainableWorker")(function* <
8+ A,
9+ E,
10+ R,
11+>(
12@@ -20,2 +24 @@ export const makeDrainableWorker = <A, E, R>(
13-): Effect.Effect<DrainableWorker<A>, never, Scope.Scope | R> =>
14- Effect.gen(function* () {
15+): Effect.fn.Return<DrainableWorker<A>, never, Scope.Scope | R> {
16diff --git a/packages/shared/src/KeyedCoalescingWorker.ts b/packages/shared/src/KeyedCoalescingWorker.ts
17index 733a3b3c53..e36859367a 100644
18--- a/packages/shared/src/KeyedCoalescingWorker.ts
19+++ b/packages/shared/src/KeyedCoalescingWorker.ts
20@@ -17 +17,3 @@ interface KeyedCoalescingWorkerState<K, V> {
21-export const makeKeyedCoalescingWorker = <K, V, E, R>(options: {
22+export const makeKeyedCoalescingWorker = Effect.fn(
23+ "KeyedCoalescingWorker.makeKeyedCoalescingWorker",
24+)(function* <K, V, E, R>(options: {
25@@ -20,2 +22 @@ export const makeKeyedCoalescingWorker = <K, V, E, R>(options: {
26-}): Effect.Effect<KeyedCoalescingWorker<K, V>, never, Scope.Scope | R> =>
27- Effect.gen(function* () {
28+}): Effect.fn.Return<KeyedCoalescingWorker<K, V>, never, Scope.Scope | R> {
29@@ -42,3 +43 @@ export const makeKeyedCoalescingWorker = <K, V, E, R>(options: {
30- Effect.flatMap((shouldRequeue) =>
31- shouldRequeue ? TxQueue.offer(queue, key) : Effect.void,
32- ),
33+ Effect.flatMap((shouldRequeue) => (shouldRequeue ? TxQueue.offer(queue, key) : Effect.void)),
34diff --git a/packages/shared/src/KeyedLock.ts b/packages/shared/src/KeyedLock.ts
35index 0411d7ef7e..27ddcd7921 100644
36--- a/packages/shared/src/KeyedLock.ts
37+++ b/packages/shared/src/KeyedLock.ts
38@@ -18,2 +18,3 @@ interface LockEntry {
39-export const make = <Key>(): Effect.Effect<KeyedLock<Key>> =>
40- Effect.gen(function* () {
41+export const make = Effect.fn("KeyedLock.make")(function* <Key>(): Effect.fn.Return<
42+ KeyedLock<Key>
43+> {
44diff --git a/packages/shared/src/Net.ts b/packages/shared/src/Net.ts
45index 7acb024d85..de56d07fad 100644
46--- a/packages/shared/src/Net.ts
47+++ b/packages/shared/src/Net.ts
48@@ -114,2 +114,3 @@ export const make = () => {
49- const isPortAvailableOnLoopback = (port: number): Effect.Effect<boolean> =>
50- Effect.gen(function* () {
51+ const isPortAvailableOnLoopback = Effect.fn("Net.isPortAvailableOnLoopback")(function* (
52+ port: number,
53+ ): Effect.fn.Return<boolean> {
54@@ -169,2 +170 @@ export const make = () => {
55- findAvailablePort: (preferred) =>
56- Effect.gen(function* () {
57+ findAvailablePort: Effect.fn("Net.findAvailablePort")(function* (preferred) {
58diff --git a/packages/shared/src/atomicWrite.test.ts b/packages/shared/src/atomicWrite.test.ts
59index 83d85b050d..5249966d15 100644
60--- a/packages/shared/src/atomicWrite.test.ts
61+++ b/packages/shared/src/atomicWrite.test.ts
62@@ -280,3 +280,4 @@ it.effect("surfaces an unreadable link instead of writing over it", () =>
63- const result = yield* Effect.exit(
64- writeFileStringAtomically({ filePath: "/home/settings.json", contents: "after" }),
65- );
66+ const failure = yield* writeFileStringAtomically({
67+ filePath: "/home/settings.json",
68+ contents: "after",
69+ }).pipe(Effect.flip);
70@@ -284 +285 @@ it.effect("surfaces an unreadable link instead of writing over it", () =>
71- assert.deepStrictEqual(result, Exit.fail(readLinkFailure));
72+ assert.deepStrictEqual(failure, readLinkFailure);
73diff --git a/packages/shared/src/devHome.ts b/packages/shared/src/devHome.ts
74index e35e63681b..1bf729a3a4 100644
75--- a/packages/shared/src/devHome.ts
76+++ b/packages/shared/src/devHome.ts
77@@ -36 +36 @@ const pointsAtLinkedWorktree = (gitFileContents: string, path: Path.Path): boole
78-export const resolveGitWorktreePath = (
79+export const resolveGitWorktreePath = Effect.fn("devHome.resolveGitWorktreePath")(function* (
80@@ -38,2 +38 @@ export const resolveGitWorktreePath = (
81-): Effect.Effect<string | undefined, never, FileSystem.FileSystem | Path.Path> =>
82- Effect.gen(function* () {
83+): Effect.fn.Return<string | undefined, never, FileSystem.FileSystem | Path.Path> {
84@@ -68 +67 @@ export const resolveGitWorktreePath = (
85-export const resolveWorktreeT3Home = (
86+export const resolveWorktreeT3Home = Effect.fn("devHome.resolveWorktreeT3Home")(function* (
87@@ -70,2 +69 @@ export const resolveWorktreeT3Home = (
88-): Effect.Effect<string | undefined, never, FileSystem.FileSystem | Path.Path> =>
89- Effect.gen(function* () {
90+): Effect.fn.Return<string | undefined, never, FileSystem.FileSystem | Path.Path> {
91diff --git a/packages/shared/src/shell.ts b/packages/shared/src/shell.ts
92index 983ba0598e..48155c4337 100644
93--- a/packages/shared/src/shell.ts
94+++ b/packages/shared/src/shell.ts
95@@ -514,2 +514,3 @@ const PathDirectoryListings = Context.Reference<
96-export const withPathDirectoryListings = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
97- Effect.gen(function* () {
98+export const withPathDirectoryListings =
99+ /* Directory reads are cached inside one shell operation; a span for each lookup adds noise. */
100+ Effect.fnUntraced(function* <A, E, R>(effect: Effect.Effect<A, E, R>) {
101diff --git a/packages/shared/src/symlink.ts b/packages/shared/src/symlink.ts
102index bc947b0596..7dfc7606e7 100644
103--- a/packages/shared/src/symlink.ts
104+++ b/packages/shared/src/symlink.ts
105@@ -20,2 +20,3 @@ const isNotASymlink = (error: PlatformError.PlatformError) =>
106-export const resolveSymlinkTarget = (filePath: string) =>
107- Effect.gen(function* () {
108+export const resolveSymlinkTarget = Effect.fn("symlink.resolveSymlinkTarget")(function* (
109+ filePath: string,
110+) {
111diff --git a/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestProvider.ts b/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestProvider.ts
112index 4a060b23a4..d083384697 100644
113--- a/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestProvider.ts
114+++ b/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestProvider.ts
115@@ -179,2 +179,4 @@ export const make = Effect.gen(function* () {
116- const diffScope = (input: { readonly cwd: string; readonly number: number }) =>
117- Effect.gen(function* () {
118+ const diffScope = Effect.fn("AzureDevOpsPullRequestProvider.diffScope")(function* (input: {
119+ readonly cwd: string;
120+ readonly number: number;
121+ }) {
122@@ -308,2 +310,2 @@ export const make = Effect.gen(function* () {
123- getChangeRequest: (input) =>
124- Effect.gen(function* () {
125+ getChangeRequest: Effect.fn("AzureDevOpsPullRequestProvider.getChangeRequest")(
126+ function* (input) {
127@@ -349 +351,3 @@ export const make = Effect.gen(function* () {
128- }).pipe(Effect.mapError(fail("getChangeRequest"))),
129+ },
130+ Effect.mapError(fail("getChangeRequest")),
131+ ),
132@@ -383,2 +387,2 @@ export const make = Effect.gen(function* () {
133- getDiff: (input) =>
134- Effect.gen(function* () {
135+ getDiff: Effect.fn("AzureDevOpsPullRequestProvider.getDiff")(
136+ function* (input) {
137@@ -482 +486,3 @@ export const make = Effect.gen(function* () {
138- }).pipe(Effect.mapError(fail("getDiff"))),
139+ },
140+ Effect.mapError(fail("getDiff")),
141+ ),
142@@ -484,2 +490,2 @@ export const make = Effect.gen(function* () {
143- getDiffFileContents: (input) =>
144- Effect.gen(function* () {
145+ getDiffFileContents: Effect.fn("AzureDevOpsPullRequestProvider.getDiffFileContents")(
146+ function* (input) {
147@@ -499 +505,3 @@ export const make = Effect.gen(function* () {
148- }).pipe(Effect.mapError(fail("getDiffFileContents"))),
149+ },
150+ Effect.mapError(fail("getDiffFileContents")),
151+ ),
152@@ -506,2 +514,2 @@ export const make = Effect.gen(function* () {
153- getFileRevisions: (input) =>
154- Effect.gen(function* () {
155+ getFileRevisions: Effect.fn("AzureDevOpsPullRequestProvider.getFileRevisions")(
156+ function* (input) {
157@@ -528 +536,3 @@ export const make = Effect.gen(function* () {
158- }).pipe(Effect.mapError(fail("getFileRevisions"))),
159+ },
160+ Effect.mapError(fail("getFileRevisions")),
161+ ),
162diff --git a/packages/source-control-bitbucket/src/server/BitbucketApi.ts b/packages/source-control-bitbucket/src/server/BitbucketApi.ts
163index e3f64c731f..eaa607e3ef 100644
164--- a/packages/source-control-bitbucket/src/server/BitbucketApi.ts
165+++ b/packages/source-control-bitbucket/src/server/BitbucketApi.ts
166@@ -605 +605 @@ function originOf(value: string): string | null {
167-function responseError(
168+const responseError = Effect.fn("BitbucketApi.responseError")(function* (
169@@ -608,2 +608 @@ function responseError(
170-): Effect.Effect<never, BitbucketApiError> {
171- return Effect.gen(function* () {
172+): Effect.fn.Return<never, BitbucketApiError> {
173@@ -633 +631,0 @@ function responseError(
174-}
175@@ -1018,2 +1016 @@ export const make = Effect.gen(function* () {
176- createPullRequest: (input) =>
177- Effect.gen(function* () {
178+ createPullRequest: Effect.fn("BitbucketApi.createPullRequest")(function* (input) {
179@@ -1081,2 +1078,2 @@ export const make = Effect.gen(function* () {
180- checkoutPullRequest: (input) =>
181- Effect.gen(function* () {
182+ checkoutPullRequest: Effect.fn("BitbucketApi.checkoutPullRequest")(
183+ function* (input) {
184@@ -1132 +1129,3 @@ export const make = Effect.gen(function* () {
185- }).pipe(
186+ },
187+ (effect, input) =>
188+ effect.pipe(
189@@ -1142,0 +1142 @@ export const make = Effect.gen(function* () {
190+ ),
191diff --git a/packages/source-control-forgejo/src/server/ForgejoSourceControlProvider.ts b/packages/source-control-forgejo/src/server/ForgejoSourceControlProvider.ts
192index 957694c60e..ba8465e9cc 100644
193--- a/packages/source-control-forgejo/src/server/ForgejoSourceControlProvider.ts
194+++ b/packages/source-control-forgejo/src/server/ForgejoSourceControlProvider.ts
195@@ -309,2 +309,2 @@ export const make = Effect.gen(function* () {
196- listChangeRequests: (input) =>
197- Effect.gen(function* () {
198+ listChangeRequests: Effect.fn("ForgejoSourceControlProvider.listChangeRequests")(
199+ function* (input) {
200@@ -337 +337,3 @@ export const make = Effect.gen(function* () {
201- }).pipe(mapError("listChangeRequests", input.cwd)),
202+ },
203+ (effect, input) => effect.pipe(mapError("listChangeRequests", input.cwd)),
204+ ),
205@@ -343,2 +345,2 @@ export const make = Effect.gen(function* () {
206- createChangeRequest: (input) =>
207- Effect.gen(function* () {
208+ createChangeRequest: Effect.fn("ForgejoSourceControlProvider.createChangeRequest")(
209+ function* (input) {
210@@ -360,3 +362,5 @@ export const make = Effect.gen(function* () {
211- }).pipe(mapError("createChangeRequest", input.cwd)),
212- getRepositoryCloneUrls: (input) =>
213- Effect.gen(function* () {
214+ },
215+ (effect, input) => effect.pipe(mapError("createChangeRequest", input.cwd)),
216+ ),
217+ getRepositoryCloneUrls: Effect.fn("ForgejoSourceControlProvider.getRepositoryCloneUrls")(
218+ function* (input) {
219@@ -367,3 +371,5 @@ export const make = Effect.gen(function* () {
220- }).pipe(mapError("getRepositoryCloneUrls", input.cwd)),
221- createRepository: (input) =>
222- Effect.gen(function* () {
223+ },
224+ (effect, input) => effect.pipe(mapError("getRepositoryCloneUrls", input.cwd)),
225+ ),
226+ createRepository: Effect.fn("ForgejoSourceControlProvider.createRepository")(
227+ function* (input) {
228@@ -390,3 +396,5 @@ export const make = Effect.gen(function* () {
229- }).pipe(mapError("createRepository", input.cwd)),
230- getDefaultBranch: (input) =>
231- Effect.gen(function* () {
232+ },
233+ (effect, input) => effect.pipe(mapError("createRepository", input.cwd)),
234+ ),
235+ getDefaultBranch: Effect.fn("ForgejoSourceControlProvider.getDefaultBranch")(
236+ function* (input) {
237@@ -398,3 +406,5 @@ export const make = Effect.gen(function* () {
238- }).pipe(mapError("getDefaultBranch", input.cwd)),
239- checkoutChangeRequest: (input) =>
240- Effect.gen(function* () {
241+ },
242+ (effect, input) => effect.pipe(mapError("getDefaultBranch", input.cwd)),
243+ ),
244+ checkoutChangeRequest: Effect.fn("ForgejoSourceControlProvider.checkoutChangeRequest")(
245+ function* (input) {
246@@ -471 +481,3 @@ export const make = Effect.gen(function* () {
247- }).pipe(mapError("checkoutChangeRequest", input.cwd)),
248+ },
249+ (effect, input) => effect.pipe(mapError("checkoutChangeRequest", input.cwd)),
250+ ),
251diff --git a/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts b/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts
252index efcc1b9e2d..41683de176 100644
253--- a/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts
254+++ b/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts
255@@ -233,7 +233,3 @@ export const make = Effect.gen(function* () {
256- const readPages = <S extends Schema.Codec<Paged<unknown>, unknown, never, never>>(
257- operation: string,
258- input: Pick<ProviderRepositoryRef, "host">,
259- path: string,
260- schema: S,
261- ) =>
262- Effect.gen(function* () {
263+ const readPages = Effect.fn("GitCafePullRequestProvider.readPages")(function* <
264+ S extends Schema.Codec<Paged<unknown>, unknown, never, never>,
265+ >(operation: string, input: Pick<ProviderRepositoryRef, "host">, path: string, schema: S) {
266@@ -245,6 +241 @@ export const make = Effect.gen(function* () {
267- const batch: S["Type"] = yield* read(
268- operation,
269- input,
270- { path: `${path}?${query}` },
271- schema,
272- );
273+ const batch: S["Type"] = yield* read(operation, input, { path: `${path}?${query}` }, schema);
274diff --git a/packages/source-control-github/src/server/GitHubPullRequestApi.ts b/packages/source-control-github/src/server/GitHubPullRequestApi.ts
275index 0e73babffc..4eb3aec349 100644
276--- a/packages/source-control-github/src/server/GitHubPullRequestApi.ts
277+++ b/packages/source-control-github/src/server/GitHubPullRequestApi.ts
278@@ -1027 +1027 @@ export const make = Effect.gen(function* () {
279- const readPages = <Page, E, R>(
280+ const readPages = Effect.fn("GitHubPullRequestApi.readPages")(function* <Page, E, R>(
281@@ -1036,2 +1036 @@ export const make = Effect.gen(function* () {
282- ) =>
283- Effect.gen(function* () {
284+ ) {
285@@ -1210,2 +1209 @@ export const make = Effect.gen(function* () {
286- (input) =>
287- Effect.gen(function* () {
288+ Effect.fn("GitHubPullRequestApi.getPullRequestDiffFileContents")(function* (input) {
289@@ -1279,3 +1277 @@ export const make = Effect.gen(function* () {
290- input.changeType === "new"
291- ? Effect.succeed("")
292- : readFile(baseRef ?? "", input.oldPath),
293+ input.changeType === "new" ? Effect.succeed("") : readFile(baseRef ?? "", input.oldPath),
294@@ -1297 +1293 @@ export const make = Effect.gen(function* () {
295- const readAllCheckContexts = (
296+ const readAllCheckContexts = Effect.fn("GitHubPullRequestApi.readAllCheckContexts")(function* (
297@@ -1300,2 +1296 @@ export const make = Effect.gen(function* () {
298- ) =>
299- Effect.gen(function* () {
300+ ) {
301@@ -1393 +1388 @@ export const make = Effect.gen(function* () {
302- const listHeadsByBranch = (input: {
303+ const listHeadsByBranch = Effect.fn("GitHubPullRequestApi.listHeadsByBranch")(function* (input: {
304@@ -1398,2 +1393 @@ export const make = Effect.gen(function* () {
305- }) =>
306- Effect.gen(function* () {
307+ }) {
308@@ -1428 +1422,2 @@ export const make = Effect.gen(function* () {
309- const listActionRequiredRuns = (input: {
310+ const listActionRequiredRuns = Effect.fn("GitHubPullRequestApi.listActionRequiredRuns")(
311+ function* (input: {
312@@ -1434,2 +1429 @@ export const make = Effect.gen(function* () {
313- }) =>
314- Effect.gen(function* () {
315+ }) {
316@@ -1467 +1461,2 @@ export const make = Effect.gen(function* () {
317- });
318+ },
319+ );
320@@ -1593,2 +1588,4 @@ export const make = Effect.gen(function* () {
321- const getPullRequestSummary: GitHubPullRequestApi["Service"]["getPullRequestSummary"] = (input) =>
322- Effect.gen(function* () {
323+ const getPullRequestSummary: GitHubPullRequestApi["Service"]["getPullRequestSummary"] = Effect.fn(
324+ "GitHubPullRequestApi.getPullRequestSummary",
325+ )(
326+ function* (input) {
327@@ -1614 +1611,6 @@ export const make = Effect.gen(function* () {
328- }).pipe((effect) => batchedRead({ ...input, operation: "getPullRequestSummary" }, effect));
329+ },
330+ (effect, input) =>
331+ effect.pipe((effect) =>
332+ batchedRead({ ...input, operation: "getPullRequestSummary" }, effect),
333+ ),
334+ );
335@@ -1684 +1686 @@ export const make = Effect.gen(function* () {
336- const collect = (
337+ const collect = Effect.fn("GitHubPullRequestApi.collect")(function* (
338@@ -1690,2 +1692 @@ export const make = Effect.gen(function* () {
339- ) =>
340- Effect.gen(function* () {
341+ ) {
342@@ -1862,2 +1863,2 @@ export const make = Effect.gen(function* () {
343- listPullRequestStats: (input) =>
344- Effect.gen(function* () {
345+ listPullRequestStats: Effect.fn("GitHubPullRequestApi.listPullRequestStats")(
346+ function* (input) {
347@@ -1891 +1892,6 @@ export const make = Effect.gen(function* () {
348- }).pipe((effect) => batchedRead({ ...input, operation: "listPullRequestStats" }, effect)),
349+ },
350+ (effect, input) =>
351+ effect.pipe((effect) =>
352+ batchedRead({ ...input, operation: "listPullRequestStats" }, effect),
353+ ),
354+ ),
355@@ -1938,2 +1944,2 @@ export const make = Effect.gen(function* () {
356- getPullRequestActivity: (input) =>
357- Effect.gen(function* () {
358+ getPullRequestActivity: Effect.fn("GitHubPullRequestApi.getPullRequestActivity")(
359+ function* (input) {
360@@ -1984 +1990,2 @@ export const make = Effect.gen(function* () {
361- }),
362+ },
363+ ),
364@@ -2060,2 +2067,2 @@ export const make = Effect.gen(function* () {
365- listReviewThreadComments: (input) =>
366- Effect.gen(function* () {
367+ listReviewThreadComments: Effect.fn("GitHubPullRequestApi.listReviewThreadComments")(
368+ function* (input) {
369@@ -2160 +2167,2 @@ export const make = Effect.gen(function* () {
370- }),
371+ },
372+ ),
373diff --git a/packages/source-control-github/src/server/GitHubRequestPolicy.ts b/packages/source-control-github/src/server/GitHubRequestPolicy.ts
374index 831ba010b9..a34467aaa4 100644
375--- a/packages/source-control-github/src/server/GitHubRequestPolicy.ts
376+++ b/packages/source-control-github/src/server/GitHubRequestPolicy.ts
377@@ -263 +263,2 @@ export const make = Effect.gen(function* () {
378- const request: GitHubRequestPolicy["Service"]["request"] = <A, E, R>(
379+ // The REST/GraphQL transport span owns request and quota metadata.
380+ const request: GitHubRequestPolicy["Service"]["request"] = Effect.fnUntraced(function* <A, E, R>(
381@@ -266,2 +267 @@ export const make = Effect.gen(function* () {
382- ) =>
383- Effect.gen(function* () {
384+ ) {
385@@ -280,2 +280,2 @@ export const make = Effect.gen(function* () {
386- const settle = (lease: number, answer: GitHubAnswer<A>) =>
387- Effect.gen(function* () {
388+ // Settlement annotates the request span with status and rate-limit attributes.
389+ const settle = Effect.fnUntraced(function* (lease: number, answer: GitHubAnswer<A>) {
390@@ -322,3 +322 @@ export const make = Effect.gen(function* () {
391- Effect.andThen(
392- limits.check(key, input.allowReserve ? { allowPaused: true } : undefined),
393- ),
394+ Effect.andThen(limits.check(key, input.allowReserve ? { allowPaused: true } : undefined)),
395@@ -341,3 +339 @@ export const make = Effect.gen(function* () {
396- Effect.fail(
397- new GitHubApiRequestError({ ...context, cause: new Cause.TimeoutError() }),
398- ),
399+ Effect.fail(new GitHubApiRequestError({ ...context, cause: new Cause.TimeoutError() })),
400diff --git a/packages/source-control-github/src/server/GitHubSourceControlProvider.ts b/packages/source-control-github/src/server/GitHubSourceControlProvider.ts
401index 8c8aa06cd9..b4c2b5107f 100644
402--- a/packages/source-control-github/src/server/GitHubSourceControlProvider.ts
403+++ b/packages/source-control-github/src/server/GitHubSourceControlProvider.ts
404@@ -930,2 +930,2 @@ export const make = Effect.gen(function* () {
405- createChangeRequest: (input) =>
406- Effect.gen(function* () {
407+ createChangeRequest: Effect.fn("GitHubSourceControlProvider.createChangeRequest")(
408+ function* (input) {
409@@ -956 +956,3 @@ export const make = Effect.gen(function* () {
410- }).pipe(
411+ },
412+ (effect, input) =>
413+ effect.pipe(
414@@ -961,2 +963,3 @@ export const make = Effect.gen(function* () {
415- getRepositoryCloneUrls: (input) =>
416- Effect.gen(function* () {
417+ ),
418+ getRepositoryCloneUrls: Effect.fn("GitHubSourceControlProvider.getRepositoryCloneUrls")(
419+ function* (input) {
420@@ -975 +978,3 @@ export const make = Effect.gen(function* () {
421- }).pipe(
422+ },
423+ (effect, input) =>
424+ effect.pipe(
425@@ -980,2 +985,3 @@ export const make = Effect.gen(function* () {
426- createRepository: (input) =>
427- Effect.gen(function* () {
428+ ),
429+ createRepository: Effect.fn("GitHubSourceControlProvider.createRepository")(
430+ function* (input) {
431@@ -1009 +1015,3 @@ export const make = Effect.gen(function* () {
432- }).pipe(
433+ },
434+ (effect, input) =>
435+ effect.pipe(
436@@ -1014,2 +1022,3 @@ export const make = Effect.gen(function* () {
437- getDefaultBranch: (input) =>
438- Effect.gen(function* () {
439+ ),
440+ getDefaultBranch: Effect.fn("GitHubSourceControlProvider.getDefaultBranch")(
441+ function* (input) {
442@@ -1024 +1033,3 @@ export const make = Effect.gen(function* () {
443- }).pipe(Effect.mapError(providerError("getDefaultBranch", input.cwd))),
444+ },
445+ (effect, input) => effect.pipe(Effect.mapError(providerError("getDefaultBranch", input.cwd))),
446+ ),
447diff --git a/packages/source-control-github/src/server/gitHubChangeRequestTemplate.ts b/packages/source-control-github/src/server/gitHubChangeRequestTemplate.ts
448index 284f386058..5b81c198da 100644
449--- a/packages/source-control-github/src/server/gitHubChangeRequestTemplate.ts
450+++ b/packages/source-control-github/src/server/gitHubChangeRequestTemplate.ts
451@@ -95 +95,2 @@ type DirectoryTemplateResult =
452-function readTemplateDirectory(input: {
453+const readTemplateDirectory = Effect.fn("gitHubChangeRequestTemplate.readTemplateDirectory")(
454+ function* (input: {
455@@ -100,2 +101 @@ function readTemplateDirectory(input: {
456-}): Effect.Effect<DirectoryTemplateResult, GitCommandError> {
457- return Effect.gen(function* () {
458+ }): Effect.fn.Return<DirectoryTemplateResult, GitCommandError> {
459@@ -125,2 +125,2 @@ function readTemplateDirectory(input: {
460- });
461-}
462+ },
463+);
464diff --git a/packages/source-control-github/src/server/gitHubConditionalChecks.ts b/packages/source-control-github/src/server/gitHubConditionalChecks.ts
465index ebef5ba5f7..46b4feea69 100644
466--- a/packages/source-control-github/src/server/gitHubConditionalChecks.ts
467+++ b/packages/source-control-github/src/server/gitHubConditionalChecks.ts
468@@ -88,2 +88,4 @@ export const makeChecksRevalidator = Effect.gen(function* () {
469- const get = (endpoint: string, head = false) =>
470- Effect.gen(function* () {
471+ const get = Effect.fn("gitHubConditionalChecks.get")(function* (
472+ endpoint: string,
473+ head = false,
474+ ) {
475diff --git a/packages/source-control-github/src/server/gitHubGraphQlClient.ts b/packages/source-control-github/src/server/gitHubGraphQlClient.ts
476index f1f6ee8944..427369ea51 100644
477--- a/packages/source-control-github/src/server/gitHubGraphQlClient.ts
478+++ b/packages/source-control-github/src/server/gitHubGraphQlClient.ts
479@@ -67,2 +67 @@ export class GitHubGraphQlAuth extends GraphQLMiddleware.Service<
480- execute: ({ request, next }) =>
481- Effect.gen(function* () {
482+ execute: Effect.fn("GitHubGraphQlAuth.execute")(function* ({ request, next }) {
483@@ -101 +100 @@ export class GitHubGraphQlAuth extends GraphQLMiddleware.Service<
484- }).pipe(Effect.withSpan("GitHubGraphQlAuth.execute")),
485+ }),
486@@ -113 +112 @@ export class GitHubGraphQlAuth extends GraphQLMiddleware.Service<
487-const refused = (
488+const refused = Effect.fn("gitHubGraphQlClient.refused")(function* (
489@@ -116,2 +115 @@ const refused = (
490-) =>
491- Effect.gen(function* () {
492+) {
493@@ -154 +152 @@ const refused = (
494-const capResponse = (
495+const capResponse = Effect.fn("gitHubGraphQlClient.capResponse")(function* (
496@@ -156,2 +154 @@ const capResponse = (
497-) =>
498- Effect.gen(function* () {
499+) {
500diff --git a/packages/source-control-gitlab/src/server/GitLabPullRequestCli.ts b/packages/source-control-gitlab/src/server/GitLabPullRequestCli.ts
501index 2a2418c6bf..a49d175eda 100644
502--- a/packages/source-control-gitlab/src/server/GitLabPullRequestCli.ts
503+++ b/packages/source-control-gitlab/src/server/GitLabPullRequestCli.ts
504@@ -1083,2 +1083 @@ export const make = Effect.gen(function* () {
505- setReaction: (input) =>
506- Effect.gen(function* () {
507+ setReaction: Effect.fn("GitLabPullRequestCli.setReaction")(function* (input) {
508@@ -1162,2 +1161,3 @@ export const make = Effect.gen(function* () {
509- getMergeRequestDiffFileContents: (input) =>
510- Effect.gen(function* () {
511+ getMergeRequestDiffFileContents: Effect.fn(
512+ "GitLabPullRequestCli.getMergeRequestDiffFileContents",
513+ )(function* (input) {
514@@ -1274,2 +1274,2 @@ export const make = Effect.gen(function* () {
515- runMergeRequestAction: (input) =>
516- Effect.gen(function* () {
517+ runMergeRequestAction: Effect.fn("GitLabPullRequestCli.runMergeRequestAction")(
518+ function* (input) {
519@@ -1301 +1301,2 @@ export const make = Effect.gen(function* () {
520- }),
521+ },
522+ ),
523@@ -1339,2 +1340 @@ export const make = Effect.gen(function* () {
524- submitReview: (input) =>
525- Effect.gen(function* () {
526+ submitReview: Effect.fn("GitLabPullRequestCli.submitReview")(function* (input) {
527diff --git a/packages/tailscale/src/tailscale.ts b/packages/tailscale/src/tailscale.ts
528index 4da4b8f5b7..4e8606e252 100644
529--- a/packages/tailscale/src/tailscale.ts
530+++ b/packages/tailscale/src/tailscale.ts
531@@ -213,2 +213,4 @@ export const parseTailscaleStatus = (
532-const readTailscaleOutput = (subcommand: "status" | "debug", args: readonly string[]) =>
533- Effect.gen(function* () {
534+const readTailscaleOutput = Effect.fn("tailscale.readTailscaleOutput")(function* (
535+ subcommand: "status" | "debug",
536+ args: readonly string[],
537+) {
538@@ -311 +313 @@ export function buildTailscaleHttpsBaseUrl(input: {
539-const runTailscaleCommand = (
540+const runTailscaleCommand = Effect.fn("tailscale.runTailscaleCommand")(function* (
541@@ -314,2 +316 @@ const runTailscaleCommand = (
542-): Effect.Effect<void, TailscaleCommandError, ChildProcessSpawner.ChildProcessSpawner> =>
543- Effect.gen(function* () {
544+): Effect.fn.Return<void, TailscaleCommandError, ChildProcessSpawner.ChildProcessSpawner> {
545@@ -375 +376 @@ export const ensureTailscaleServe = (input: {
546-export const disableTailscaleServe = (
547+export const disableTailscaleServe = Effect.fn("tailscale.disableTailscaleServe")(function* (
548@@ -379,2 +380 @@ export const disableTailscaleServe = (
549-): Effect.Effect<void, TailscaleCommandError, ChildProcessSpawner.ChildProcessSpawner> =>
550- Effect.gen(function* () {
551+): Effect.fn.Return<void, TailscaleCommandError, ChildProcessSpawner.ChildProcessSpawner> {
552@@ -388,5 +388,2 @@ export const disableTailscaleServe = (
553-export const probeTailscaleHttpsEndpoint = (input: {
554- readonly baseUrl: string;
555- readonly timeout?: Duration.Input;
556-}): Effect.Effect<boolean, never, HttpClient.HttpClient> =>
557- Effect.gen(function* () {
558+export const probeTailscaleHttpsEndpoint = Effect.fn("tailscale.probeTailscaleHttpsEndpoint")(
559+ function* (input: { readonly baseUrl: string; readonly timeout?: Duration.Input }) {
560@@ -404 +401,3 @@ export const probeTailscaleHttpsEndpoint = (input: {
561- }).pipe(Effect.orElseSucceed(() => false));
562+ },
563+ Effect.orElseSucceed(() => false),
564+);

Model: GPT-6.1 Sol. Harness: Codex.

Checks

25 successful · 9 skipped
All checks passed
5b256b8
Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire—5.0 KiB—6.8 KiB✅
CodexThread snapshot wire—3.8 KiB—4.9 KiB✅
CodexLive turn WebSocket wire—1.2 KiB—2.0 KiB✅
CodexLive turn WebSocket decoded—20.9 KiB—29.3 KiB✅
CodexLive turn messages—2—8✅
ClaudeTotal thread wire—5.0 KiB—6.8 KiB✅
ClaudeThread snapshot wire—3.8 KiB—4.9 KiB✅
ClaudeLive turn WebSocket wire—1.2 KiB—2.0 KiB✅
ClaudeLive turn WebSocket decoded—21.2 KiB—29.3 KiB✅
ClaudeLive turn messages—2—8✅

Baseline: unavailable · PR result: 5b256b8 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: b40e8846-83f8-4511-8ae4-b589f1f67b25
📥 Commits

Reviewing files that changed from the base of the PR and between 96cd12277a1bb24dd683877d871db09e73bd5025 and 5b256b8e25b25ec222e48034dce4b27cbec2f30c.

📒 Files selected for processing: 1
  • packages/source-control-gitlab/src/server/GitLabPullRequestCli.ts

Limit details: You’ve used all 10 included reviews currently available.

📝 Walkthrough

Effect workflows across shared utilities, source-control providers, GitHub request handling, and Tailscale now use Effect.fn or Effect.fnUntraced. Existing operational behavior remains unchanged. One test now compares the underlying failure value.

Changes: Effect.fn Refactor

Layer / File(s)Summary
Shared utility workflows
packages/shared/src/DrainableWorker.ts, packages/shared/src/KeyedCoalescingWorker.ts, packages/shared/src/KeyedLock.ts, packages/shared/src/Net.ts, packages/shared/src/atomicWrite.test.ts, packages/shared/src/devHome.ts, packages/shared/src/shell.ts, packages/shared/src/symlink.ts
Shared workflows now use Effect.fn or Effect.fnUntraced. The atomic-write test compares the underlying failure value.
Source-control provider workflows
packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestProvider.ts, packages/source-control-bitbucket/src/server/BitbucketApi.ts, packages/source-control-forgejo/src/server/ForgejoSourceControlProvider.ts, packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts, packages/source-control-gitlab/src/server/GitLabPullRequestCli.ts
Provider operations now use named Effect.fn generators. Existing request, pagination, and error handling remain unchanged.
GitHub request and response handling
packages/source-control-github/src/server/GitHubRequestPolicy.ts, packages/source-control-github/src/server/gitHubConditionalChecks.ts, packages/source-control-github/src/server/gitHubGraphQlClient.ts
Request policy, conditional-check, and GraphQL workflows now use Effect.fn declarations. Existing request and response handling remains unchanged.
GitHub pull request workflows
packages/source-control-github/src/server/GitHubPullRequestApi.ts, packages/source-control-github/src/server/GitHubSourceControlProvider.ts, packages/source-control-github/src/server/gitHubChangeRequestTemplate.ts
Pull request API operations, provider methods, and template reading now use Effect.fn declarations. Existing pagination, validation, batching, and error handling remain unchanged.
Tailscale command workflows
packages/tailscale/src/tailscale.ts
Output, command, serve, and endpoint-probe workflows now use Effect.fn. Existing timeout and error handling remain unchanged.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Suggested reviewers: maria-rcks

Merge Risk: ⚪ Minimal · up to 5b256

No actionable merge-blocking risk is identified.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely identifies the Effect refactor and tracing changes across shared and source-control operations.
Description checkPassedThe description follows the required structure, explains the problem and change, documents scope and approval, and reports focused verification results with test counts and limitations.
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Usage-based review receipt

Note

This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.

Comment @coderabbitai help to get the list of available commands.

Synced from GitHub

Approvability

A prior Macroscope approval was dismissed. Re-evaluating 5b256b8…

Added 4 commits

  • fix(github): preserve transport span request metadata525f520
  • refactor(effect): trace shared and source-control operations53da95f
  • test(shared): compare atomic write failures without tracing metadata96cd122
  • refactor(gitlab): trace merge request actions added on main5b256b8