Skip to content

Enable stricter Effect LSP rules#2596

Merged
juliusmarminge merged 9 commits intomainfrom
t3code/498d8931
May 8, 2026
Merged

Enable stricter Effect LSP rules#2596
juliusmarminge merged 9 commits intomainfrom
t3code/498d8931

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented May 8, 2026

Summary

  • Replaced barrel-style effect imports with explicit subpath imports across server, desktop, and integration code.
  • Moved desktop server-exposure modules into a dedicated serverExposure folder and updated all call sites.
  • Tightened a few Node/runtime edge cases while updating tests and diagnostics annotations to match the new import style.

Testing

  • Not run
  • Expected validation for this change set: bun fmt
  • Expected validation for this change set: bun lint
  • Expected validation for this change set: bun typecheck
  • Expected validation for backend changes: bun run test

Note

Medium Risk
Mostly mechanical import refactors, but it also changes auth token claim encoding/validation and replaces Date.now()/new Date() timing with Effect Clock/DateTime in orchestration and checkpoint flows, which could affect runtime behavior and test expectations.

Overview
Migrates the codebase away from effect barrel imports to explicit module namespace imports (e.g. effect/Effect, effect/Layer) across desktop, server, and integration code, and adds @effect-diagnostics suppressions where node builtins/global timers are intentionally used.

Makes time/UUID/JSON behavior more Effect-driven and deterministic. Orchestration and checkpoint code now uses Clock.currentTimeMillis/DateTime.now for timestamps and metrics timing, tests replace real Date.now()/new Date() with fixed ISO values, and several scripts/tests switch to Effect/Node-safe timer APIs.

Tightens a few runtime behaviors. Session credential issuance now Schema-encodes claims (with explicit error mapping) and verifies exp via DateTime.make instead of unsafe construction; CLI publishing/keybindings config writing now uses shared pretty-JSON Schema helpers; and project CLI errors are normalized into a tagged ProjectCommandError with ISO timestamps derived from DateTime.

Reviewed by Cursor Bugbot for commit 1ccd9f4. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Enable stricter Effect LSP rules across the monorepo by migrating to namespace imports

  • Moves the @effect/language-service plugin config from per-package tsconfig.json files into tsconfig.base.json, enabling shared stricter rules (e.g. preferSchemaOverJson, missingEffectServiceDependency, globalErrorInEffectFailure as errors) across all packages.
  • Migrates all Effect imports from barrel-style named imports (import { Effect, Layer } from 'effect') to module-qualified namespace imports (import * as Effect from 'effect/Effect') across hundreds of files to satisfy the new namespaceImportPackages rule.
  • Replaces new Date().toISOString() and Date.now() with Effect DateTime and Clock services throughout production code, and replaces JSON.stringify/JSON.parse with Schema.encodeUnknownSync/Schema.decodeUnknownSync to satisfy preferSchemaOverJson.
  • Introduces typed tagged errors (ProjectSetupScriptRunnerError, ProviderMaintenanceCommandError, ProjectCommandError, etc.) to replace generic Error in failure channels, satisfying globalErrorInEffectFailure.
  • Converts several provider snapshot builder functions (makePendingClaudeProvider, buildUnavailableProviderSnapshot, etc.) from plain values to Effect-returning functions, as their timestamps now require the Effect clock.
  • Test suites replace setTimeout/Date.now() polling with TestClock.adjust, Effect.yieldNow, and fixed ISO string literals for determinism.
  • Risk: Callers of makePersistedServerRuntimeState, makeManagedServerProvider, and the provider snapshot builders must now yield* an Effect instead of using a plain value; the TerminalCwdError cause-message method now returns a boolean instead of a string.

Macroscope summarized 1ccd9f4.

- Replace barrel imports across server and test code
- Add diagnostics overrides for Node-only scripts and probes
- Tighten auth/session encoding and deterministic test fixtures
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5a993893-7315-45b2-85ce-3f3855045d9a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/498d8931

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

@juliusmarminge juliusmarminge changed the title Switch Effect imports to explicit subpaths Enable stricter Effect LSP rules May 8, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Metrics record bogus duration if interrupted early
    • Moved processingStartedAtMs acquisition to Clock.currentTimeMillis before Effect.exit via Effect.flatMap, ensuring the timestamp is always valid even if the inner effect is interrupted before its first yield.
  • ✅ Fixed: Published package.json loses pretty-print formatting
    • Replaced Schema.UnknownFromJsonString with a custom PrettyUnknownFromJsonString schema that passes space:2 to SchemaGetter.stringifyJson, restoring 2-space indented JSON output while satisfying the preferSchemaOverJson lint rule.

Create PR

Or push these changes by commenting:

@cursor push b63ec8265e
Preview (b63ec8265e)
diff --git a/apps/server/scripts/cli.ts b/apps/server/scripts/cli.ts
--- a/apps/server/scripts/cli.ts
+++ b/apps/server/scripts/cli.ts
@@ -8,6 +8,8 @@
 import * as Option from "effect/Option";
 import * as Path from "effect/Path";
 import * as Schema from "effect/Schema";
+import * as SchemaGetter from "effect/SchemaGetter";
+import * as SchemaTransformation from "effect/SchemaTransformation";
 import { Command, Flag } from "effect/unstable/cli";
 import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
 
@@ -35,6 +37,16 @@
   overrides: Record<string, string>;
 }
 
+const PrettyUnknownFromJsonString = Schema.String.pipe(
+  Schema.decodeTo(
+    Schema.Unknown,
+    new SchemaTransformation.Transformation<unknown, string>(
+      SchemaGetter.parseJson(),
+      SchemaGetter.stringifyJson({ space: 2 }),
+    ),
+  ),
+);
+
 class CliError extends Data.TaggedError("CliError")<{
   readonly message: string;
   readonly cause?: unknown;
@@ -235,7 +247,7 @@
           yield* fs.writeFileString(backupPath, original);
           yield* fs.writeFileString(
             packageJsonPath,
-            `${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(pkg)}\n`,
+            `${Schema.encodeUnknownSync(PrettyUnknownFromJsonString)(pkg)}\n`,
           );
           yield* Effect.log("[cli] Prepared package.json for publish");
 

diff --git a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts
--- a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts
+++ b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts
@@ -98,7 +98,6 @@
 
   const processEnvelope = (envelope: CommandEnvelope): Effect.Effect<void> => {
     const dispatchStartSequence = commandReadModel.snapshotSequence;
-    let processingStartedAtMs = 0;
     const aggregateRef = commandToAggregateRef(envelope.command);
     const baseMetricAttributes = {
       commandType: envelope.command.type,
@@ -119,163 +118,172 @@
       }
     });
 
-    return Effect.exit(
-      Effect.gen(function* () {
-        processingStartedAtMs = yield* Clock.currentTimeMillis;
-        yield* Effect.annotateCurrentSpan({
-          "orchestration.command_id": envelope.command.commandId,
-          "orchestration.command_type": envelope.command.type,
-          "orchestration.aggregate_kind": aggregateRef.aggregateKind,
-          "orchestration.aggregate_id": aggregateRef.aggregateId,
-        });
+    return Clock.currentTimeMillis.pipe(
+      Effect.flatMap((processingStartedAtMs) =>
+        Effect.exit(
+          Effect.gen(function* () {
+            yield* Effect.annotateCurrentSpan({
+              "orchestration.command_id": envelope.command.commandId,
+              "orchestration.command_type": envelope.command.type,
+              "orchestration.aggregate_kind": aggregateRef.aggregateKind,
+              "orchestration.aggregate_id": aggregateRef.aggregateId,
+            });
 
-        const existingReceipt = yield* commandReceiptRepository.getByCommandId({
-          commandId: envelope.command.commandId,
-        });
-        if (Option.isSome(existingReceipt)) {
-          if (existingReceipt.value.status === "accepted") {
-            return {
-              sequence: existingReceipt.value.resultSequence,
-            };
-          }
-          return yield* new OrchestrationCommandPreviouslyRejectedError({
-            commandId: envelope.command.commandId,
-            detail: existingReceipt.value.error ?? "Previously rejected.",
-          });
-        }
-
-        const eventBase = yield* decideOrchestrationCommand({
-          command: envelope.command,
-          readModel: commandReadModel,
-        });
-        const eventBases = Array.isArray(eventBase) ? eventBase : [eventBase];
-        const committedCommand = yield* sql
-          .withTransaction(
-            Effect.gen(function* () {
-              const committedEvents: OrchestrationEvent[] = [];
-              let nextCommandReadModel = commandReadModel;
-
-              for (const nextEvent of eventBases) {
-                const savedEvent = yield* eventStore.append(nextEvent);
-                nextCommandReadModel = yield* projectEvent(nextCommandReadModel, savedEvent);
-                yield* projectionPipeline.projectEvent(savedEvent);
-                committedEvents.push(savedEvent);
+            const existingReceipt = yield* commandReceiptRepository.getByCommandId({
+              commandId: envelope.command.commandId,
+            });
+            if (Option.isSome(existingReceipt)) {
+              if (existingReceipt.value.status === "accepted") {
+                return {
+                  sequence: existingReceipt.value.resultSequence,
+                };
               }
-
-              const lastSavedEvent = committedEvents.at(-1) ?? null;
-              if (lastSavedEvent === null) {
-                return yield* new OrchestrationCommandInvariantError({
-                  commandType: envelope.command.type,
-                  detail: "Command produced no events.",
-                });
-              }
-
-              yield* commandReceiptRepository.upsert({
+              return yield* new OrchestrationCommandPreviouslyRejectedError({
                 commandId: envelope.command.commandId,
-                aggregateKind: lastSavedEvent.aggregateKind,
-                aggregateId: lastSavedEvent.aggregateId,
-                acceptedAt: lastSavedEvent.occurredAt,
-                resultSequence: lastSavedEvent.sequence,
-                status: "accepted",
-                error: null,
+                detail: existingReceipt.value.error ?? "Previously rejected.",
               });
+            }
 
-              return {
-                committedEvents,
-                lastSequence: lastSavedEvent.sequence,
-                nextCommandReadModel,
-              } as const;
-            }),
-          )
-          .pipe(
-            Effect.catchTag("SqlError", (sqlError) =>
-              Effect.fail(
-                toPersistenceSqlError("OrchestrationEngine.processEnvelope:transaction")(sqlError),
-              ),
-            ),
-          );
+            const eventBase = yield* decideOrchestrationCommand({
+              command: envelope.command,
+              readModel: commandReadModel,
+            });
+            const eventBases = Array.isArray(eventBase) ? eventBase : [eventBase];
+            const committedCommand = yield* sql
+              .withTransaction(
+                Effect.gen(function* () {
+                  const committedEvents: OrchestrationEvent[] = [];
+                  let nextCommandReadModel = commandReadModel;
 
-        commandReadModel = committedCommand.nextCommandReadModel;
-        for (const [index, event] of committedCommand.committedEvents.entries()) {
-          yield* PubSub.publish(eventPubSub, event);
-          if (index === 0) {
-            yield* Metric.update(
-              Metric.withAttributes(
-                orchestrationCommandAckDuration,
-                metricAttributes({
-                  ...baseMetricAttributes,
-                  ackEventType: event.type,
-                }),
-              ),
-              Duration.millis(Math.max(0, (yield* Clock.currentTimeMillis) - envelope.startedAtMs)),
-            );
-          }
-        }
-        return { sequence: committedCommand.lastSequence };
-      }).pipe(Effect.withSpan(`orchestration.command.${envelope.command.type}`)),
-    ).pipe(
-      Effect.flatMap((exit) =>
-        Effect.gen(function* () {
-          const outcome = Exit.isSuccess(exit)
-            ? "success"
-            : Cause.hasInterruptsOnly(exit.cause)
-              ? "interrupt"
-              : "failure";
-          yield* Metric.update(
-            Metric.withAttributes(
-              orchestrationCommandDuration,
-              metricAttributes(baseMetricAttributes),
-            ),
-            Duration.millis(Math.max(0, (yield* Clock.currentTimeMillis) - processingStartedAtMs)),
-          );
-          yield* Metric.update(
-            Metric.withAttributes(
-              orchestrationCommandsTotal,
-              metricAttributes({
-                ...baseMetricAttributes,
-                outcome,
-              }),
-            ),
-            1,
-          );
+                  for (const nextEvent of eventBases) {
+                    const savedEvent = yield* eventStore.append(nextEvent);
+                    nextCommandReadModel = yield* projectEvent(nextCommandReadModel, savedEvent);
+                    yield* projectionPipeline.projectEvent(savedEvent);
+                    committedEvents.push(savedEvent);
+                  }
 
-          if (Exit.isSuccess(exit)) {
-            yield* Deferred.succeed(envelope.result, exit.value);
-            return;
-          }
+                  const lastSavedEvent = committedEvents.at(-1) ?? null;
+                  if (lastSavedEvent === null) {
+                    return yield* new OrchestrationCommandInvariantError({
+                      commandType: envelope.command.type,
+                      detail: "Command produced no events.",
+                    });
+                  }
 
-          const error = Cause.squash(exit.cause) as OrchestrationDispatchError;
-          if (!Schema.is(OrchestrationCommandPreviouslyRejectedError)(error)) {
-            yield* reconcileReadModelAfterDispatchFailure.pipe(
-              Effect.catch(() =>
-                Effect.logWarning(
-                  "failed to reconcile orchestration read model after dispatch failure",
-                ).pipe(
-                  Effect.annotateLogs({
+                  yield* commandReceiptRepository.upsert({
                     commandId: envelope.command.commandId,
-                    snapshotSequence: commandReadModel.snapshotSequence,
-                  }),
+                    aggregateKind: lastSavedEvent.aggregateKind,
+                    aggregateId: lastSavedEvent.aggregateId,
+                    acceptedAt: lastSavedEvent.occurredAt,
+                    resultSequence: lastSavedEvent.sequence,
+                    status: "accepted",
+                    error: null,
+                  });
+
+                  return {
+                    committedEvents,
+                    lastSequence: lastSavedEvent.sequence,
+                    nextCommandReadModel,
+                  } as const;
+                }),
+              )
+              .pipe(
+                Effect.catchTag("SqlError", (sqlError) =>
+                  Effect.fail(
+                    toPersistenceSqlError("OrchestrationEngine.processEnvelope:transaction")(
+                      sqlError,
+                    ),
+                  ),
                 ),
-              ),
-            );
+              );
 
-            if (Schema.is(OrchestrationCommandInvariantError)(error)) {
-              yield* commandReceiptRepository
-                .upsert({
-                  commandId: envelope.command.commandId,
-                  aggregateKind: aggregateRef.aggregateKind,
-                  aggregateId: aggregateRef.aggregateId,
-                  acceptedAt: yield* nowIso,
-                  resultSequence: commandReadModel.snapshotSequence,
-                  status: "rejected",
-                  error: error.message,
-                })
-                .pipe(Effect.catch(() => Effect.void));
+            commandReadModel = committedCommand.nextCommandReadModel;
+            for (const [index, event] of committedCommand.committedEvents.entries()) {
+              yield* PubSub.publish(eventPubSub, event);
+              if (index === 0) {
+                yield* Metric.update(
+                  Metric.withAttributes(
+                    orchestrationCommandAckDuration,
+                    metricAttributes({
+                      ...baseMetricAttributes,
+                      ackEventType: event.type,
+                    }),
+                  ),
+                  Duration.millis(
+                    Math.max(0, (yield* Clock.currentTimeMillis) - envelope.startedAtMs),
+                  ),
+                );
+              }
             }
-          }
+            return { sequence: committedCommand.lastSequence };
+          }).pipe(Effect.withSpan(`orchestration.command.${envelope.command.type}`)),
+        ).pipe(
+          Effect.flatMap((exit) =>
+            Effect.gen(function* () {
+              const outcome = Exit.isSuccess(exit)
+                ? "success"
+                : Cause.hasInterruptsOnly(exit.cause)
+                  ? "interrupt"
+                  : "failure";
+              yield* Metric.update(
+                Metric.withAttributes(
+                  orchestrationCommandDuration,
+                  metricAttributes(baseMetricAttributes),
+                ),
+                Duration.millis(
+                  Math.max(0, (yield* Clock.currentTimeMillis) - processingStartedAtMs),
+                ),
+              );
+              yield* Metric.update(
+                Metric.withAttributes(
+                  orchestrationCommandsTotal,
+                  metricAttributes({
+                    ...baseMetricAttributes,
+                    outcome,
+                  }),
+                ),
+                1,
+              );
 
-          yield* Deferred.fail(envelope.result, error);
-        }),
+              if (Exit.isSuccess(exit)) {
+                yield* Deferred.succeed(envelope.result, exit.value);
+                return;
+              }
+
+              const error = Cause.squash(exit.cause) as OrchestrationDispatchError;
+              if (!Schema.is(OrchestrationCommandPreviouslyRejectedError)(error)) {
+                yield* reconcileReadModelAfterDispatchFailure.pipe(
+                  Effect.catch(() =>
+                    Effect.logWarning(
+                      "failed to reconcile orchestration read model after dispatch failure",
+                    ).pipe(
+                      Effect.annotateLogs({
+                        commandId: envelope.command.commandId,
+                        snapshotSequence: commandReadModel.snapshotSequence,
+                      }),
+                    ),
+                  ),
+                );
+
+                if (Schema.is(OrchestrationCommandInvariantError)(error)) {
+                  yield* commandReceiptRepository
+                    .upsert({
+                      commandId: envelope.command.commandId,
+                      aggregateKind: aggregateRef.aggregateKind,
+                      aggregateId: aggregateRef.aggregateId,
+                      acceptedAt: yield* nowIso,
+                      resultSequence: commandReadModel.snapshotSequence,
+                      status: "rejected",
+                      error: error.message,
+                    })
+                    .pipe(Effect.catch(() => Effect.void));
+                }
+              }
+
+              yield* Deferred.fail(envelope.result, error);
+            }),
+          ),
+        ),
       ),
     );
   };

You can send follow-ups to the cloud agent here.

const processEnvelope = (envelope: CommandEnvelope): Effect.Effect<void> => {
const dispatchStartSequence = commandReadModel.snapshotSequence;
const processingStartedAtMs = Date.now();
let processingStartedAtMs = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics record bogus duration if interrupted early

Medium Severity

processingStartedAtMs is initialized to 0 and only assigned inside the inner Effect.gen via yield* Clock.currentTimeMillis. If the fiber is interrupted before that first yield completes, the value stays 0. The outer Effect.flatMap continuation always runs (via Effect.exit) and computes Clock.currentTimeMillis - 0, recording an astronomically large duration metric (~1.78 trillion ms). Previously, Date.now() was called eagerly before entering the Effect, guaranteeing a valid start time.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f44c0a7. Configure here.

Comment thread apps/server/scripts/cli.ts Outdated
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented May 8, 2026

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

You can customize Macroscope's approvability policy. Learn more.

- Switch test imports to the current TestClock module path
- Use the NetService context tag consistently across server, desktop, and ssh code
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for 1 of the 2 issues found in the latest run.

  • ✅ Fixed: Busy-wait polling replaces timed sleep in tests
    • Replaced Effect.runPromise(Effect.yieldNow) with Effect.runPromise(Effect.sleep("10 millis")) in all three polling functions to restore a real 10ms delay between poll iterations.

Create PR

Or push these changes by commenting:

@cursor push 5580ad54e2
Preview (5580ad54e2)
diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
--- a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
+++ b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
@@ -170,7 +170,7 @@
     if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) {
       throw new Error("Timed out waiting for thread state.");
     }
-    await Effect.runPromise(Effect.yieldNow);
+    await Effect.runPromise(Effect.sleep("10 millis"));
     return poll();
   };
   return poll();
@@ -192,7 +192,7 @@
     if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) {
       throw new Error("Timed out waiting for orchestration event.");
     }
-    await Effect.runPromise(Effect.yieldNow);
+    await Effect.runPromise(Effect.sleep("10 millis"));
     return poll();
   };
   return poll();
@@ -239,7 +239,7 @@
     if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) {
       throw new Error(`Timed out waiting for git ref '${ref}'.`);
     }
-    await Effect.runPromise(Effect.yieldNow);
+    await Effect.runPromise(Effect.sleep("10 millis"));
     return poll();
   };
   return poll();

You can send follow-ups to the cloud agent here.

Comment thread apps/server/integration/TestProviderAdapter.integration.ts
Comment thread apps/server/src/orchestration/Layers/CheckpointReactor.test.ts Outdated
@juliusmarminge
Copy link
Copy Markdown
Member Author

@cursor push 5580ad5

Replace Effect.yieldNow (near-instant microtask resolution) with
Effect.sleep("10 millis") to restore a real delay between poll
iterations in waitForThread, waitForEvent, and waitForGitRefExists.

Without a real delay, these polling loops spin as tight busy-waits,
starving the event loop of time to process I/O callbacks from git
operations and database writes.

Applied via @cursor push command
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: nowIsoSync uses Effect.runSync bypassing runtime clock
    • Replaced nowIsoSync (which used Effect.runSync) with an effectful nowIso constant consumed via Effect.flatMap inside Effect.catch handlers, ensuring DateTime.now is evaluated within the fiber's runtime clock context and is controllable by TestClock.

Create PR

Or push these changes by commenting:

@cursor push 24ad635e33
Preview (24ad635e33)
diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.ts
--- a/apps/server/src/orchestration/Layers/CheckpointReactor.ts
+++ b/apps/server/src/orchestration/Layers/CheckpointReactor.ts
@@ -33,7 +33,7 @@
 import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts";
 import { WorkspaceEntries } from "../../workspace/Services/WorkspaceEntries.ts";
 
-const nowIsoSync = () => Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso)));
+const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso));
 
 type ReactorInput =
   | {
@@ -724,12 +724,14 @@
     if (event.type === "thread.checkpoint-revert-requested") {
       yield* handleRevertRequested(event).pipe(
         Effect.catch((error) =>
-          appendRevertFailureActivity({
-            threadId: event.payload.threadId,
-            turnCount: event.payload.turnCount,
-            detail: error.message,
-            createdAt: nowIsoSync(),
-          }),
+          Effect.flatMap(nowIso, (createdAt) =>
+            appendRevertFailureActivity({
+              threadId: event.payload.threadId,
+              turnCount: event.payload.turnCount,
+              detail: error.message,
+              createdAt,
+            }),
+          ),
         ),
       );
       return;
@@ -743,12 +745,14 @@
     if (event.type === "thread.turn-diff-completed") {
       yield* captureCheckpointFromPlaceholder(event).pipe(
         Effect.catch((error) =>
-          appendCaptureFailureActivity({
-            threadId: event.payload.threadId,
-            turnId: event.payload.turnId,
-            detail: error.message,
-            createdAt: nowIsoSync(),
-          }).pipe(Effect.catch(() => Effect.void)),
+          Effect.flatMap(nowIso, (createdAt) =>
+            appendCaptureFailureActivity({
+              threadId: event.payload.threadId,
+              turnId: event.payload.turnId,
+              detail: error.message,
+              createdAt,
+            }),
+          ).pipe(Effect.catch(() => Effect.void)),
         ),
       );
     }
@@ -767,12 +771,14 @@
       yield* refreshLocalGitStatusFromTurnCompletion(event);
       yield* captureCheckpointFromTurnCompletion(event).pipe(
         Effect.catch((error) =>
-          appendCaptureFailureActivity({
-            threadId: event.threadId,
-            turnId,
-            detail: error.message,
-            createdAt: nowIsoSync(),
-          }).pipe(Effect.catch(() => Effect.void)),
+          Effect.flatMap(nowIso, (createdAt) =>
+            appendCaptureFailureActivity({
+              threadId: event.threadId,
+              turnId,
+              detail: error.message,
+              createdAt,
+            }),
+          ).pipe(Effect.catch(() => Effect.void)),
         ),
       );
       return;

You can send follow-ups to the cloud agent here.

Comment thread apps/server/src/orchestration/Layers/CheckpointReactor.ts Outdated
- Replace sync timestamp reads with Effect-based time generation
- Update provider initial snapshots and unavailable snapshots to return effects
- Adjust managed provider tests for the new async snapshot contract
- Reject invalid session exp claims
- Use safer DateTime parsing across diagnostics, adapter, and settings
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Missing try-catch around BigInt conversion causes uncaught throw
    • Restored try-catch around BigInt(text) in unixNanoToDateTime so invalid non-integer strings return null instead of throwing an uncaught SyntaxError.

Create PR

Or push these changes by commenting:

@cursor push 5250529987
Preview (5250529987)
diff --git a/apps/server/src/diagnostics/TraceDiagnostics.ts b/apps/server/src/diagnostics/TraceDiagnostics.ts
--- a/apps/server/src/diagnostics/TraceDiagnostics.ts
+++ b/apps/server/src/diagnostics/TraceDiagnostics.ts
@@ -90,8 +90,12 @@
   const text = toStringValue(value);
   if (!text) return null;
 
-  const millis = Number(BigInt(text) / 1_000_000n);
-  return Option.getOrNull(DateTime.make(millis));
+  try {
+    const millis = Number(BigInt(text) / 1_000_000n);
+    return Option.getOrNull(DateTime.make(millis));
+  } catch {
+    return null;
+  }
 }
 
 function readExitTag(exit: unknown): string | null {

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 00d4b68. Configure here.

Comment thread apps/server/src/diagnostics/TraceDiagnostics.ts Outdated
Comment thread apps/server/src/diagnostics/TraceDiagnostics.ts
- Centralize stable 2-space JSON encoding in `packages/shared`
- Switch CLI and release-version scripts to the shared helper
- Keep keybindings config writes using the same pretty encoder
- Set a default `forceKillAfter` when spawning the Codex app server
- Rely on child-process lifecycle from the spawned handle instead of an explicit release kill
@juliusmarminge juliusmarminge merged commit 25b02f4 into main May 8, 2026
12 checks passed
@juliusmarminge juliusmarminge deleted the t3code/498d8931 branch May 8, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants