Skip to content

Sync fork with upstream/main and preserve multi-provider integrations#39

Merged
aaditagrawal merged 46 commits intomainfrom
codex/upstream-sync-2026-03-29
Mar 30, 2026
Merged

Sync fork with upstream/main and preserve multi-provider integrations#39
aaditagrawal merged 46 commits intomainfrom
codex/upstream-sync-2026-03-29

Conversation

@aaditagrawal
Copy link
Copy Markdown
Owner

@aaditagrawal aaditagrawal commented Mar 29, 2026

Summary

  • merge upstream/main into the fork integration branch while preserving the fork's multi-provider architecture
  • carry forward server-authoritative settings, expanded provider snapshot coverage, archived-thread support, and merged web/settings UI updates
  • align Copilot provider probing with runtime fallback resolution so status no longer warns when the provider can start from an implicit CLI path

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • bun x vitest run apps/server/src/provider/Layers/ProviderRegistry.test.ts --config apps/server/vitest.config.ts

Summary by CodeRabbit

  • New Features

    • Thread archive/unarchive flow with an Archived Threads panel and archive confirmations.
    • Thread navigation shortcuts (previous/next + jump keys mod+1…mod+9).
    • Resizable app sidebar, per-project favicons, and desktop update UI including sidebar pill and install confirmations.
  • Improvements

    • Settings relocated to a dedicated Settings area (General + Archived) with compat sync to unified settings.
    • Shell PATH fixes now support Linux in addition to macOS; improved desktop update state syncing.
  • Chores

    • Enforce LF line endings, package version bumps, ignore release-mock, add mock update server script.

juliusmarminge and others added 30 commits March 25, 2026 23:58
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: smalitobules <207318034+smalitobules@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
…otgg#1488)

Co-authored-by: Julius Marminge <julius0216@outlook.com>
…SIS installer during update process (pingdotgg#1461)

Co-authored-by: Daniel Schwarz <daniel.schwarz@serviceware-se.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
…to open context menu (pingdotgg#873)

Co-authored-by: Julius Marminge <julius0216@outlook.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds thread-archive commands/events and DB migrations, exposes generateThreadTitle across text-generation providers, broadens provider probing and settings compatibility, standardizes worker forking/start semantics, improves Git output truncation and bootstrap FD handling, refactors web settings/routes, and adds tests, utilities, and a mock update server.

Changes

Cohort / File(s) Summary
Repo config & tooling
\.gitattributes, \.gitignore, package.json, scripts/mock-update-server.ts, docs/effect-fn-checklist.md
Enforce LF line endings, ignore release-mock/, bump Effect‑TS pins and add trustedDependency, add mock update server script, and add effect-fn refactor checklist.
Trusted list
.github/VOUCHED.td
Reordered an entry for alphabetical order and added github:shivamhwp.
CI / PR sizing workflow
.github/workflows/pr-size.yml
Switch PR size computation to a local git-based diff approach, fetch full history, compute test/non-test changed-line counts via pathspec exclusions, and checkout with fetch-depth:0.
Desktop app
apps/desktop/package.json, apps/desktop/src/preload.ts
Bump desktop package version and add UPDATE_CHECK_CHANNEL constant exposed to preload bridge.
Shell / env helpers
apps/desktop/src/syncShellEnvironment.ts, apps/desktop/src/syncShellEnvironment.test.ts, packages/shared/src/shell.ts
Enable login-shell PATH hydration on Linux (in addition to macOS), add resolveLoginShell helper and tests.
Bootstrap / FD handling
apps/server/src/bootstrap.ts, apps/server/src/bootstrap.test.ts
Add direct-stream fallback for stdin FD, attempt safe duplication via NFS with guarded ENXIO/EINVAL/EPERM fallback, and add test for duplication-failure behavior.
Git core & truncation
apps/server/src/git/Layers/GitCore.ts, apps/server/src/git/Services/GitCore.ts, related tests
Add max-output-byte caps and optional truncation behavior for git stdout/stderr, pass options through executeGit, and apply caps to specific git operations.
Text-generation: thread titles
apps/server/src/git/... (ClaudeTextGeneration.ts, CodexTextGeneration.ts, SessionTextGeneration.ts, RoutingTextGeneration.ts, Prompts.ts, Utils.ts, Services/TextGeneration.ts, tests)
Introduce generateThreadTitle across providers, shared prompt builder, sanitizeThreadTitle utility, service API/types, and add tests.
Provider registry & adapters
apps/server/src/provider/... (ProviderRegistry.ts, ProviderService.ts, CodexAdapter.ts, tests)`
Expand provider probing to many providers, periodic refresh/caching, unified CLI probe path and warnings, map Codex process/stderr to runtime.warning, and start forked workers immediately.
Orchestration / projections / commands
packages/contracts/src/orchestration.ts, apps/server/src/orchestration/*, apps/server/src/persistence/*, migrations
Add thread.archive / thread.unarchive commands and thread.archived/thread.unarchived events, add archivedAt field through contracts, projections, persistence, migrations (add column + index), invariants, and projector changes.
Server fork semantics & tests
apps/server/vitest.config.ts, many apps/server/src/** tests and reactor files
Standardize forking to start fibers immediately, increase test timeouts and disable file parallelism, and adjust many tests for longer waits and deterministic fiber joins.
Web settings & routes
apps/web/src/appSettings.ts, hooks/useSettings.ts, routeTree.gen.ts, routes/settings*.tsx, components/settings/*
Introduce compat layer syncing AppSettings with unified server settings, migrate legacy keys, split settings into /settings/general and /settings/archived, add modular settings panels and sidebar nav.
Web UI, hooks & actions
apps/web/src/hooks/useThreadActions.ts, components/*, lib/desktopUpdateReactQuery.ts, modelSelection.ts
Add archive/unarchive/delete thread flows and hook, desktop-update React Query helpers, ProjectFavicon, AppSidebarLayout, settings panels, model-selection helpers, and related UI/tests.
Frontend storage & utilities
apps/web/src/lib/storage.ts, hooks/useLocalStorage.ts, terminalStateStore.ts, timestampFormat.ts, terminal-links.ts
Add storage resolution/type guards, SSR-safe localStorage handling, safer terminal state storage, relative-time label helper, and broaden Windows path detection.
Shared utilities & tests
packages/shared/src/String.ts, packages/shared/src/shell.ts, tests
Add truncate() utility and resolveLoginShell helper with tests.
Contracts & IPC
packages/contracts/*
Add archivedAt to OrchestrationThread, new archive/unarchive commands/events/payloads, confirmThreadArchive client setting, thread keybinding command types, and ContextMenuItem.disabled.
Misc tests & small fixes
many test files across apps
Widespread test updates and additions to exercise new features and adjusted timing (examples: projector, checkpointing, provider adapters, GitManager).

Sequence Diagram(s)

sequenceDiagram
    participant UI as Client/UI
    participant API as Server API
    participant Engine as Orchestration Engine
    participant DB as Projection DB
    rect rgba(128, 200, 255, 0.5)
    UI->>API: dispatch `thread.archive` command
    API->>Engine: enqueue command for processing
    Engine->>Engine: decideOrchestrationCommand -> emit `thread.archived` event
    Engine->>DB: persist orchestration event
    DB-->>Engine: ack
    Engine->>DB: projection upsert sets `archived_at`
    DB-->>API: projection read returns updated thread
    API-->>UI: respond with updated read model / confirmation
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐇 I hopped through commits and nibbled a thread,
Added titles, probes, and a new archived shed.
Workers spring to life the moment they're spun,
DBs mark the threads where old chatter's done —
A rabbit cheers: deploy, then take a nap in the sun.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/upstream-sync-2026-03-29

@github-actions github-actions bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ effective changed lines (test files excluded in mixed PRs). labels Mar 29, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/desktop/src/syncShellEnvironment.ts (1)

10-15: ⚠️ Potential issue | 🟠 Major

Linux fallback shell is still macOS-specific.

Line 11 enables Linux, but Line 14 still falls back to "/bin/zsh". On Linux hosts without zsh (and missing SHELL), env sync silently no-ops due to the catch block.

💡 Proposed fix
-import { readEnvironmentFromLoginShell, ShellEnvironmentReader } from "@t3tools/shared/shell";
+import {
+  readEnvironmentFromLoginShell,
+  resolveLoginShell,
+  ShellEnvironmentReader,
+} from "@t3tools/shared/shell";
@@
   const platform = options.platform ?? process.platform;
   if (platform !== "darwin" && platform !== "linux") return;
 
   try {
-    const shell = env.SHELL?.trim() || "/bin/zsh";
+    const shell = resolveLoginShell(platform, env.SHELL);
+    if (!shell) return;
     const shellEnvironment = (options.readEnvironment ?? readEnvironmentFromLoginShell)(shell, [
       "PATH",
       "SSH_AUTH_SOCK",
     ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/syncShellEnvironment.ts` around lines 10 - 15, The fallback
shell literal is macOS-specific ("/bin/zsh") so on Linux hosts without zsh the
env.SHELL missing causes readEnvironmentFromLoginShell to fail; update the shell
selection in syncShellEnvironment (the const shell = env.SHELL?.trim() ||
"/bin/zsh" line) to pick a sensible platform-specific default (e.g., use
env.SHELL if set, otherwise choose "/bin/zsh" for darwin and "/bin/sh" or
"/bin/bash" for linux) before calling readEnvironmentFromLoginShell; ensure the
change references env.SHELL and the call site (readEnvironmentFromLoginShell or
options.readEnvironment) so the fallback is conditional on the platform.
apps/web/src/store.ts (1)

641-670: ⚠️ Potential issue | 🟠 Major

Include archivedAt in the structural-sharing equality guard.

Line 641 adds archivedAt, but the reuse check (Lines 651-670) omits it. That can skip state updates when archive status changes.

🔧 Suggested fix
       if (
         existing &&
         existing.id === normalizedThread.id &&
         existing.codexThreadId === normalizedThread.codexThreadId &&
         existing.projectId === normalizedThread.projectId &&
         existing.title === normalizedThread.title &&
         areUnknownValuesEqual(existing.modelSelection, normalizedThread.modelSelection) &&
         existing.runtimeMode === normalizedThread.runtimeMode &&
         existing.interactionMode === normalizedThread.interactionMode &&
         existing.session === normalizedThread.session &&
         existing.messages === normalizedThread.messages &&
         existing.proposedPlans === normalizedThread.proposedPlans &&
         existing.error === normalizedThread.error &&
         existing.createdAt === normalizedThread.createdAt &&
+        existing.archivedAt === normalizedThread.archivedAt &&
         existing.updatedAt === normalizedThread.updatedAt &&
         existing.latestTurn === normalizedThread.latestTurn &&
         existing.lastVisitedAt === normalizedThread.lastVisitedAt &&
         existing.branch === normalizedThread.branch &&
         existing.worktreePath === normalizedThread.worktreePath &&
         existing.turnDiffSummaries === normalizedThread.turnDiffSummaries &&
         existing.activities === normalizedThread.activities
       ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/store.ts` around lines 641 - 670, The structural-sharing
equality guard that compares existing and normalizedThread is missing
archivedAt, so state updates can be skipped when archive status changes; update
the condition inside the reuse check (the block comparing existing.* to
normalizedThread.*) to include a comparison for existing.archivedAt ===
normalizedThread.archivedAt (referencing existing, normalizedThread, and the
archivedAt property) so the guard considers archive status when deciding to
reuse the existing thread object.
🟡 Minor comments (14)
docs/effect-fn-checklist.md-20-25 (1)

20-25: ⚠️ Potential issue | 🟡 Minor

Reserved keyword used as variable name in example.

Line 22 uses new as a variable name, which is a reserved keyword in JavaScript/TypeScript and will cause a syntax error. This could confuse developers following the example.

📝 Proposed fix
 ```ts
 // New
-const new = Effect.fn('functionName')(function* () {
+const newFn = Effect.fn('functionName')(function* () {
     ...
 })

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/effect-fn-checklist.md around lines 20 - 25, The example incorrectly
uses the reserved keyword new as a variable; change the example variable name
(e.g., rename new to newFn) where Effect.fn('functionName')(function* () { ... }) is assigned so the sample compiles without syntax errors, and update any
subsequent references in that snippet to the new identifier.


</details>

</blockquote></details>
<details>
<summary>packages/shared/src/String.ts-1-3 (1)</summary><blockquote>

`1-3`: _⚠️ Potential issue_ | _🟡 Minor_

**Guard `maxLength` against invalid values.**

On Line 1, negative (or non-integer) `maxLength` produces inconsistent truncation behavior. Add an explicit runtime check before the length comparison.



<details>
<summary>Proposed fix</summary>

```diff
 export function truncate(text: string, maxLength = 50): string {
+  if (!Number.isInteger(maxLength) || maxLength < 0) {
+    throw new RangeError("maxLength must be a non-negative integer");
+  }
   const trimmed = text.trim();
   if (trimmed.length <= maxLength) {
     return trimmed;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/String.ts` around lines 1 - 3, The truncate function
should validate its maxLength parameter before use: inside function truncate
(before computing trimmed.length and the comparison) add a runtime guard that
ensures maxLength is a finite integer >= 0 (e.g. Number.isInteger(maxLength) &&
maxLength >= 0); if the check fails, throw a RangeError (or coerce/normalize by
using Math.floor on valid numeric values) so negative or non-integer inputs
cannot produce inconsistent truncation behavior.
.github/VOUCHED.td-30-32 (1)

30-32: ⚠️ Potential issue | 🟡 Minor

Entries are not in alphabetical order.

The file header (line 11) states "Keep entries sorted alphabetically," but the new entries violate this requirement:

  • github:eggfriedrice24 should be placed between github:cursoragent (line 15) and github:gbarros-dev (line 16)
  • github:shivamhwp should be placed between github:shiroyasha9 (line 28) and github:Yash-Singh1 (line 29)
📋 Proposed fix to restore alphabetical ordering

Remove the entries from their current positions and place them in the correct alphabetical positions:

 github:cursoragent
+github:eggfriedrice24
 github:gbarros-dev
 github:github-actions[bot]
 github:shiroyasha9
+github:shivamhwp
 github:Yash-Singh1
-github:eggfriedrice24
 github:Ymit24
-github:shivamhwp
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/VOUCHED.td around lines 30 - 32, The new GitHub handle entries are
out of alphabetical order; remove github:eggfriedrice24 and github:shivamhwp
from their current positions and insert github:eggfriedrice24 between
github:cursoragent and github:gbarros-dev, and insert github:shivamhwp between
github:shiroyasha9 and github:Yash-Singh1 so the list remains sorted as required
by the file header.
apps/web/src/components/ProjectFavicon.tsx-2-2 (1)

2-2: ⚠️ Potential issue | 🟡 Minor

Reset load state when cwd/src changes.

The status state is only initialized once. If this component stays mounted and cwd changes, it can keep stale "loaded"/"error" state for the new src.

Proposed fix
-import { useState } from "react";
+import { useEffect, useState } from "react";
@@
   const [status, setStatus] = useState<"loading" | "loaded" | "error">(() =>
     loadedProjectFaviconSrcs.has(src) ? "loaded" : "loading",
   );
+
+  useEffect(() => {
+    setStatus(loadedProjectFaviconSrcs.has(src) ? "loaded" : "loading");
+  }, [src]);

Also applies to: 28-30

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ProjectFavicon.tsx` at line 2, Component
ProjectFavicon currently initializes the status state once and never resets it
when the incoming props (cwd or src) change, causing stale "loaded"/"error"
state; add a useEffect in ProjectFavicon that watches the src and cwd values and
resets the related state (e.g., call setStatus to an initial value like
"loading" or "" and reset any other related flags referenced around lines 28-30)
whenever src or cwd changes so each new image load starts from a fresh state.
apps/server/src/persistence/Migrations.ts-33-34 (1)

33-34: ⚠️ Potential issue | 🟡 Minor

Migration filename/ID mismatch.

Migration0018 is imported from 017_ProjectionThreadsArchivedAt.ts but registered with ID 18. Similarly, Migration0019 is imported from 018_ProjectionThreadsArchivedAtIndex.ts and registered with ID 19. The file prefixes are off-by-one from their registration IDs.

While this doesn't break functionality (the migrationEntries array IDs govern execution order), the inconsistent naming creates maintenance confusion. The files should be renamed:

  • 017_ProjectionThreadsArchivedAt.ts018_ProjectionThreadsArchivedAt.ts
  • 018_ProjectionThreadsArchivedAtIndex.ts019_ProjectionThreadsArchivedAtIndex.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/persistence/Migrations.ts` around lines 33 - 34, The import
filenames for Migration0018 and Migration0019 are off-by-one vs their registered
IDs; rename the source files and update imports so names match the migration
IDs: rename 017_ProjectionThreadsArchivedAt.ts →
018_ProjectionThreadsArchivedAt.ts and 018_ProjectionThreadsArchivedAtIndex.ts →
019_ProjectionThreadsArchivedAtIndex.ts, then update the import statements for
Migration0018 and Migration0019 in Migrations.ts (and any other references) to
the new filenames so the module names, file prefixes and the IDs used in the
migrationEntries registration remain consistent.
apps/server/src/provider/Layers/ProviderRegistry.ts-168-231 (1)

168-231: ⚠️ Potential issue | 🟡 Minor

This publish guard won't debounce no-op refreshes.

Each snapshot builder stamps a fresh checkedAt, so haveProvidersChanged(previousProviders, mergedProviders) at Line 428 will keep seeing a change even when the substantive provider state is identical. The 60-second sync loop will therefore continue publishing server.providersUpdated on every pass.

Also applies to: 426-430

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ProviderRegistry.ts` around lines 168 - 231,
The snapshot builders (buildDisabledSnapshot, buildWarningSnapshot,
buildReadySnapshot) always stamp a fresh checkedAt, causing
haveProvidersChanged(...) to see differences even when nothing else changed;
change the builders to accept an optional checkedAt parameter (or stop setting
checkedAt inside them) and move timestamp assignment to the caller that detects
real changes (the code path that calls haveProvidersChanged and publishes
server.providersUpdated) so checkedAt is only updated when substantive provider
state actually changes; update all call sites of
buildDisabledSnapshot/buildWarningSnapshot/buildReadySnapshot to pass through
the preserved checkedAt or only set a new timestamp when haveProvidersChanged
indicates a change.
apps/server/src/codexAppServerManager.test.ts-239-272 (1)

239-272: ⚠️ Potential issue | 🟡 Minor

This bypasses the stderr path you actually want to lock down.

Calling emitNotificationEvent directly only proves that helper serializes a notification. A regression in raw stderr classification or in the code that dispatches classified lines would still pass here. Please drive this assertion through the manager entry point that consumes stderr so the test covers the behavior changed in this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/codexAppServerManager.test.ts` around lines 239 - 272, The
test currently bypasses the stderr classification logic by calling
emitNotificationEvent directly; instead, invoke the CodexAppServerManager entry
point that consumes raw stderr lines (the production-facing method on
CodexAppServerManager that receives/processes stderr lines) so the
classification and dispatch path is exercised, keep the spy on emitEvent, feed a
raw stderr string like "fatal: permission denied" into that stderr consumer, and
assert emitEvent was called with the same expectation; replace the direct call
to emitNotificationEvent with a call to the manager's stderr-consumption method
while leaving the emitEvent spy and the expect(...) assertion unchanged.
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts-113-126 (1)

113-126: ⚠️ Potential issue | 🟡 Minor

Normalize legacy turn.completed events into the canonical shape only.

On Line 116, spreading event keeps the legacy top-level status and errorMessage fields on the normalized object. That makes this harness compatible with code that still reads the deprecated shape, so these tests can miss regressions in the canonical payload.state path.

🧹 Suggested cleanup
   const normalizeLegacyEvent = (event: LegacyProviderRuntimeEvent): ProviderRuntimeEvent => {
     if (isLegacyTurnCompletedEvent(event)) {
+      const { status, errorMessage, ...rest } = event;
       const normalized: Extract<ProviderRuntimeEvent, { type: "turn.completed" }> = {
-        ...(event as Omit<Extract<ProviderRuntimeEvent, { type: "turn.completed" }>, "payload">),
+        ...(rest as Omit<Extract<ProviderRuntimeEvent, { type: "turn.completed" }>, "payload">),
         payload: {
-          state: event.status,
-          ...(typeof event.errorMessage === "string" ? { errorMessage: event.errorMessage } : {}),
+          state: status,
+          ...(typeof errorMessage === "string" ? { errorMessage } : {}),
         },
       };
       return normalized;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts` around
lines 113 - 126, The normalizeLegacyEvent helper currently spreads the legacy
event into the normalized object which leaves deprecated top-level fields
(status, errorMessage) on the result; update the normalizeLegacyEvent
implementation (the branch guarded by isLegacyTurnCompletedEvent) to construct
the Extract<ProviderRuntimeEvent, { type: "turn.completed" }> explicitly without
spreading the original event so the returned object contains only the canonical
fields (type and payload), set payload.state = event.status and conditionally
include payload.errorMessage when present, and ensure no legacy top-level status
or errorMessage remain on the returned object.
apps/server/src/git/Layers/SessionTextGeneration.ts-15-16 (1)

15-16: ⚠️ Potential issue | 🟡 Minor

Remove unused import ThreadTitleGenerationInput.

Static analysis correctly flagged that ThreadTitleGenerationInput is imported but never used. The type is inferred from SessionTextGenerationShape["generateThreadTitle"].

-import type {
-  ThreadTitleGenerationInput,
-  ThreadTitleGenerationResult,
-} from "../Services/TextGeneration.ts";
+import type { ThreadTitleGenerationResult } from "../Services/TextGeneration.ts";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/git/Layers/SessionTextGeneration.ts` around lines 15 - 16,
Remove the unused type import ThreadTitleGenerationInput from the import list at
the top of SessionTextGeneration.ts; keep ThreadTitleGenerationResult if used
and rely on SessionTextGenerationShape["generateThreadTitle"] for the input type
inference, ensuring no other references to ThreadTitleGenerationInput remain
(search for ThreadTitleGenerationInput and delete its import).
apps/server/src/git/Layers/GitCore.test.ts-449-459 (1)

449-459: ⚠️ Potential issue | 🟡 Minor

Avoid Effect.runPromise inside an Effect.

The pipeline flagged this pattern. Using Effect.runPromise inside Effect.gen breaks Effect's structured concurrency guarantees. Consider using Effect.tryPromise or restructuring the test to keep vitest assertions outside the Effect pipeline.

Possible refactor
-        yield* Effect.promise(() =>
-          vi.waitFor(
-            async () => {
-              const details = await Effect.runPromise(core.statusDetails(source));
-              expect(details.branch).toBe(featureBranch);
-              expect(details.aheadCount).toBe(0);
-              expect(details.behindCount).toBe(1);
-            },
-            { timeout: 20_000 },
-          ),
-        );
+        yield* Effect.retry(
+          Effect.gen(function* () {
+            const details = yield* core.statusDetails(source);
+            expect(details.branch).toBe(featureBranch);
+            expect(details.aheadCount).toBe(0);
+            expect(details.behindCount).toBe(1);
+          }),
+          { times: 40, schedule: Schedule.spaced(Duration.millis(500)) },
+        );
scripts/mock-update-server.ts-13-16 (1)

13-16: ⚠️ Potential issue | 🟡 Minor

Root containment check incorrectly rejects dot-prefixed paths.

The current logic !relative(...).startsWith(".") blocks legitimate in-root paths like .well-known because any relative path starting with a dot is rejected. Instead, check explicitly for parent traversal (..) and absolute escapes.

🔧 Proposed fix
-import { resolve, relative } from "node:path";
+import { resolve, relative, isAbsolute } from "node:path";
 import { realpathSync } from "node:fs";
@@
 function isWithinRoot(filePath: string): boolean {
   try {
-    return !relative(realpathSync(root), realpathSync(filePath)).startsWith(".");
+    const rootRealPath = realpathSync(root);
+    const targetRealPath = realpathSync(filePath);
+    const rel = relative(rootRealPath, targetRealPath);
+    return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
   } catch (error) {
     mockServerLog("error", `Error checking if file is within root: ${error}`);
     return false;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/mock-update-server.ts` around lines 13 - 16, The isWithinRoot
function currently rejects any relative path that starts with "." which
incorrectly blocks valid in-root dot-prefixed names (e.g., ".well-known");
change the logic to compute rel = relative(realpathSync(root),
realpathSync(filePath)) and return true when rel does not escape to a parent
(i.e., the first path segment is not "..") and rel is not an absolute path —
e.g., return !path.isAbsolute(rel) && !rel.split(path.sep)[0].startsWith("..");
replace the old startsWith(".") check and ensure you import/use path (path.sep,
path.isAbsolute) and keep realpathSync(root) and realpathSync(filePath) as
before.
apps/web/src/components/Sidebar.logic.test.ts-810-838 (1)

810-838: ⚠️ Potential issue | 🟡 Minor

Test intent and setup are misaligned for archived-thread behavior.

At Line 837, the .filter((thread) => thread.archivedAt === null) removes archived threads before sortProjectsForSidebar runs, so this test doesn’t actually validate that the function ignores archived threads.

🧪 Suggested test fix
       [
         makeThread({
           id: ThreadId.makeUnsafe("thread-visible"),
           projectId: ProjectId.makeUnsafe("project-1"),
           updatedAt: "2026-03-09T10:02:00.000Z",
           archivedAt: null,
         }),
         makeThread({
           id: ThreadId.makeUnsafe("thread-archived"),
           projectId: ProjectId.makeUnsafe("project-2"),
           updatedAt: "2026-03-09T10:10:00.000Z",
           archivedAt: "2026-03-09T10:11:00.000Z",
         }),
-      ].filter((thread) => thread.archivedAt === null),
+      ],
       "updated_at",
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/Sidebar.logic.test.ts` around lines 810 - 838, The
test is accidentally removing archived threads before calling
sortProjectsForSidebar, so remove the .filter((thread) => thread.archivedAt ===
null) and pass the full threads array (including the archived thread created by
makeThread with archivedAt set) into sortProjectsForSidebar; ensure the
assertion checks that sorting treats the project with only archived threads as
older (i.e., the "Visible project" sorts before "Archived-only project") to
validate that sortProjectsForSidebar ignores archived threads when computing
order.
apps/web/src/components/desktopUpdate.logic.ts-8-10 (1)

8-10: ⚠️ Potential issue | 🟡 Minor

Keep the tooltip consistent with the new install-first rule.

Line 8 now treats any downloadedVersion as an installable update, but Lines 56-58 still advertise “ready to download” whenever status === "available". SidebarUpdatePill uses both helpers, so the same state can render a restart CTA with a download tooltip.

Minimal fix
 export function getDesktopUpdateButtonTooltip(state: DesktopUpdateState): string {
+  if (state.status === "available" && state.downloadedVersion) {
+    return `Update ${state.downloadedVersion} downloaded. Click to restart and install.`;
+  }
   if (state.status === "available") {
     return `Update ${state.availableVersion ?? "available"} ready to download`;
   }

Also applies to: 55-66

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/desktopUpdate.logic.ts` around lines 8 - 10, The
tooltip logic is inconsistent with the install-first rule: update the helper
used by SidebarUpdatePill so it checks state.downloadedVersion before status ===
"available" and returns a "ready to install" (or similar “ready to
restart/install”) tooltip when downloadedVersion is present; only return the
“ready to download” text when status === "available" and there is no
downloadedVersion. Adjust the helper(s) that reference state.downloadedVersion
and the status === "available" branch (the block around lines 55-66) so both
helpers consistently treat downloadedVersion as an installable state.
apps/web/src/routes/settings.tsx-16-29 (1)

16-29: ⚠️ Potential issue | 🟡 Minor

Don’t let Escape eject the user from focused form controls.

This listener runs for every keydown on a form-heavy page. Pressing Escape inside an input, select, or path field will call history.back() unless that control explicitly prevents default first, which is an easy way to kick users out of settings while they’re editing. Ignore editable targets before handling the shortcut.

Possible guard
     const onKeyDown = (event: KeyboardEvent) => {
       if (event.defaultPrevented) return;
+      const target = event.target;
+      if (
+        target instanceof HTMLElement &&
+        (target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName))
+      ) {
+        return;
+      }
       if (event.key === "Escape") {
         event.preventDefault();
         window.history.back();
       }
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/settings.tsx` around lines 16 - 29, The onKeyDown handler
used in the useEffect currently calls window.history.back() for any Escape key
press; update the onKeyDown function to early-return if the event originated
from an editable control (check event.target for tagName "INPUT", "TEXTAREA",
"SELECT", or node.isContentEditable, and also ignore elements with
role="textbox" or when an ancestor is contentEditable) before calling
event.preventDefault() and window.history.back(); keep the handler
attached/detached the same.
🧹 Nitpick comments (15)
apps/server/src/persistence/Migrations/016_CanonicalizeModelSelections.test.ts (1)

310-377: Consider de-coupling assertions from array indexes.

These checks are now index-fragile (this change already required index shifts). Prefer asserting by event_id map so fixture insertions don’t force broad churn.

♻️ Example refactor
-          const eventRows = yield* sql<{
-            readonly payloadJson: string;
-          }>`
-        SELECT payload_json AS "payloadJson"
+          const eventRows = yield* sql<{
+            readonly eventId: string;
+            readonly payloadJson: string;
+          }>`
+        SELECT
+          event_id AS "eventId",
+          payload_json AS "payloadJson"
         FROM orchestration_events
         ORDER BY rowid ASC
       `;
+          const payloadByEventId = new Map(
+            eventRows.map((row) => [row.eventId, JSON.parse(row.payloadJson)] as const),
+          );
 
-          assert.deepStrictEqual(JSON.parse(eventRows[3]!.payloadJson), {
+          assert.deepStrictEqual(payloadByEventId.get("event-thread-created"), {
             threadId: "thread-1",
             // ...
           });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/server/src/persistence/Migrations/016_CanonicalizeModelSelections.test.ts`
around lines 310 - 377, The assertions are brittle because they index into
eventRows (e.g., eventRows[3]!.payloadJson, eventRows[4]!.payloadJson, etc.);
instead build a map keyed by event_id (or whichever unique identifier exists on
each row) from eventRows first, then JSON.parse the payloadJson for each
expected event and assert against map[eventId].payloadJson — update tests
referencing eventRows[...] to use that map and the unique event_id values (e.g.,
the thread/turn ids used in the expected payloads) so fixture insertions won’t
break index positions.
apps/web/src/components/chat/MessagesTimeline.test.tsx (1)

54-97: Consider extracting shared MessagesTimeline props to reduce duplication.

Both tests repeat a large common prop set, which makes future updates error-prone.

♻️ Optional refactor sketch
+const baseTimelineProps = {
+  hasMessages: true,
+  isWorking: false,
+  activeTurnInProgress: false,
+  activeTurnStartedAt: null,
+  scrollContainer: null,
+  completionDividerBeforeEntryId: null,
+  completionSummary: null,
+  turnDiffSummaryByAssistantMessageId: new Map(),
+  nowIso: "2026-03-17T19:12:30.000Z",
+  expandedWorkGroups: {},
+  onToggleWorkGroup: () => {},
+  onOpenTurnDiff: () => {},
+  revertTurnCountByUserMessageId: new Map(),
+  onRevertUserMessage: () => {},
+  isRevertingCheckpoint: false,
+  onImageExpand: () => {},
+  markdownCwd: undefined,
+  resolvedTheme: "light" as const,
+  timestampFormat: "locale" as const,
+  workspaceRoot: undefined,
+};
...
-<MessagesTimeline
-  hasMessages
-  isWorking={false}
-  ...
-  workspaceRoot={undefined}
-/>
+<MessagesTimeline
+  {...baseTimelineProps}
+  timelineEntries={[/* test-specific entries */]}
+/>

Also applies to: 111-146

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/chat/MessagesTimeline.test.tsx` around lines 54 - 97,
Tests duplicate a large prop set for the MessagesTimeline component; extract the
shared props into a single reusable constant or factory (e.g.,
defaultTimelineProps or makeTimelineProps) in MessagesTimeline.test.tsx and
spread it into the <MessagesTimeline ... /> calls, then override only the
differing props (like timelineEntries, nowIso, expandedWorkGroups) per-test;
update both occurrences (around the current block and lines 111-146) to
import/inline the shared object so future changes only touch one place.
docs/effect-fn-checklist.md (1)

48-183: Use relative paths for better portability.

All file references use absolute paths rooted at /Users/julius/Development/Work/codething-mvp/, which are specific to one developer's environment. These links won't work for other contributors or in CI environments.

♻️ Suggested approach

Convert absolute paths to repository-relative paths. For example:

-- [x] [buildUserMessageEffect](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ClaudeAdapter.ts#L554)
+- [x] [buildUserMessageEffect](../apps/server/src/provider/Layers/ClaudeAdapter.ts#L554)

Or use paths relative to the repository root without the leading /Users/...:

-- [x] [buildUserMessageEffect](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ClaudeAdapter.ts#L554)
+- [x] [buildUserMessageEffect](apps/server/src/provider/Layers/ClaudeAdapter.ts#L554)

This will make the checklist portable across all development environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/effect-fn-checklist.md` around lines 48 - 183, The checklist contains
absolute filesystem links (e.g.,
/Users/julius/Development/Work/codething-mvp/...) which break portability;
update each link in docs/effect-fn-checklist.md to use repository-relative paths
(for example change
/Users/.../apps/server/src/provider/Layers/ClaudeAdapter.ts#L554 to
apps/server/src/provider/Layers/ClaudeAdapter.ts#L554) so references like
buildUserMessageEffect, makeClaudeAdapter, makeGitCore, handleTraceLine,
runProjectorForEvent, makeProviderService, finalizeAssistantMessage,
makeCodexAdapter, captureCheckpoint, toLogMessage, etc. remain valid across
environments and CI. Ensure no remaining absolute prefixes anywhere in the file.
apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx (1)

235-239: Assert absence of the warning message directly instead of generic "ultrathink" text.

The current negative check is case-sensitive and broad. Targeting the exact warning copy is more reliable for this scenario.

Proposed diff
     await vi.waitFor(() => {
       const text = document.body.textContent ?? "";
       expect(text).toContain("Effort");
-      expect(text).not.toContain("ultrathink");
+      expect(text).not.toContain(
+        'Your prompt contains "ultrathink" in the text. Remove it to change effort.',
+      );
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx` around
lines 235 - 239, Replace the broad, case-sensitive negative assertion that
checks for "ultrathink" inside the vi.waitFor block with an assertion that
targets the exact warning copy used by the component (e.g., the full warning
sentence shown to users) so the test explicitly verifies the absence of that
warning; locate the vi.waitFor(...) block in
CompactComposerControlsMenu.browser.tsx and change
expect(text).not.toContain("ultrathink") to expect(text).not.toContain("<exact
warning text>") (or a precise regex if punctuation/whitespace may vary) using
the same vi.waitFor and expect calls.
apps/web/src/truncateTitle.ts (1)

1-8: Prefer delegating truncateTitle to shared truncate utility.

This logic now duplicates packages/shared/src/String.ts; centralizing here will prevent drift and simplify maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/truncateTitle.ts` around lines 1 - 8, Replace the local
truncation logic in function truncateTitle by importing and delegating to the
shared truncate utility (use the shared function named truncate) instead of
manually trimming/slicing; keep the truncateTitle signature and default
maxLength, import truncate at top, and return truncate(text, maxLength) so the
module uses the centralized implementation from the shared String utility.
packages/shared/src/String.test.ts (1)

5-17: Add boundary-case tests for maxLength.

Consider adding cases for maxLength = 0 and invalid values (e.g., negative/non-integer) to lock expected behavior and prevent regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/String.test.ts` around lines 5 - 17, Add unit tests in
String.test.ts for boundary and invalid maxLength values: add a case calling
truncate("abc", 0) and assert the expected behavior (decide and lock it in —
e.g., expect(truncate("abc", 0)).toBe("") if you want zero length to return an
empty string), and add tests for invalid inputs like negative and non-integer
values (e.g., truncate("abc", -1) and truncate("abc", 2.5)) asserting the chosen
contract (either expect a thrown error via toThrow or a coerced/truncated
result). Reference the truncate function in these new tests so behavior is
explicit and future changes are caught.
apps/server/src/bootstrap.ts (1)

139-155: Consider logging or narrowing the catch scope.

The bare catch at line 146 silently swallows all errors before falling back to Net.Socket. While this "try everything" approach ensures maximum resilience, it could mask unexpected failure modes (e.g., resource exhaustion, permission issues unrelated to fd type).

Since this is intentional fallback behavior and Net.Socket is the final resort, this is acceptable—but adding a debug-level log or narrowing to expected error codes would aid troubleshooting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/bootstrap.ts` around lines 139 - 155, The catch in
makeDirectBootstrapStream currently swallows all errors from
NFS.createReadStream before falling back to new Net.Socket; update it to either
narrow the catch to expected errors (e.g., check err.code for
ENOSYS/EBADF/EPERM) or log the caught error at debug level so unexpected
failures are visible; locate the NFS.createReadStream call inside
makeDirectBootstrapStream and add a debug/processLogger.debug (or rethrow for
non-expected codes) before constructing the Net.Socket fallback.
apps/web/src/modelSelection.ts (1)

21-69: Reduce provider map duplication to prevent drift

At Line 27-Line 68, provider keys are hardcoded repeatedly. This is easy to miss during future provider additions and can desync UI options from supported providers.

♻️ Proposed refactor
 export function getCustomModelOptionsByProvider(
   settings: UnifiedSettings,
   _providers?: ReadonlyArray<ServerProvider>,
   selectedProvider?: ProviderKind | null,
   selectedModel?: string | null,
 ) {
-  return {
-    codex: getAppModelOptions(
-      "codex",
-      settings.providers.codex.customModels,
-      selectedProvider === "codex" ? selectedModel : undefined,
-    ),
-    copilot: getAppModelOptions(
-      "copilot",
-      settings.providers.copilot.customModels,
-      selectedProvider === "copilot" ? selectedModel : undefined,
-    ),
-    claudeAgent: getAppModelOptions(
-      "claudeAgent",
-      settings.providers.claudeAgent.customModels,
-      selectedProvider === "claudeAgent" ? selectedModel : undefined,
-    ),
-    cursor: getAppModelOptions(
-      "cursor",
-      settings.providers.cursor.customModels,
-      selectedProvider === "cursor" ? selectedModel : undefined,
-    ),
-    opencode: getAppModelOptions(
-      "opencode",
-      settings.providers.opencode.customModels,
-      selectedProvider === "opencode" ? selectedModel : undefined,
-    ),
-    geminiCli: getAppModelOptions(
-      "geminiCli",
-      settings.providers.geminiCli.customModels,
-      selectedProvider === "geminiCli" ? selectedModel : undefined,
-    ),
-    amp: getAppModelOptions(
-      "amp",
-      settings.providers.amp.customModels,
-      selectedProvider === "amp" ? selectedModel : undefined,
-    ),
-    kilo: getAppModelOptions(
-      "kilo",
-      settings.providers.kilo.customModels,
-      selectedProvider === "kilo" ? selectedModel : undefined,
-    ),
-  } as const;
+  const providerOrder = [
+    "codex",
+    "copilot",
+    "claudeAgent",
+    "cursor",
+    "opencode",
+    "geminiCli",
+    "amp",
+    "kilo",
+  ] as const;
+
+  return Object.fromEntries(
+    providerOrder.map((provider) => [
+      provider,
+      getAppModelOptions(
+        provider,
+        settings.providers[provider].customModels,
+        selectedProvider === provider ? selectedModel : undefined,
+      ),
+    ]),
+  ) as {
+    readonly [P in (typeof providerOrder)[number]]: ReturnType<typeof getAppModelOptions>;
+  };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/modelSelection.ts` around lines 21 - 69, The provider map in
getCustomModelOptionsByProvider is duplicated and brittle; change it to iterate
over settings.providers (or a single source-of-truth provider list) and
dynamically build the returned object by calling getAppModelOptions(providerKey,
settings.providers[providerKey].customModels, selectedProvider === providerKey ?
selectedModel : undefined); keep the same function signature
(getCustomModelOptionsByProvider) and types (UnifiedSettings, ServerProvider,
ProviderKind), ensure the resulting object is typed/const-cast similarly to the
original return, and preserve behavior for determining selectedModel per
provider.
apps/server/src/orchestration/decider.ts (1)

193-236: Consider using the archived-state invariants to enforce valid transitions.

The requireThreadArchived and requireThreadNotArchived helpers were added in commandInvariants.ts but are not used here. Currently:

  • thread.archive allows re-archiving an already archived thread
  • thread.unarchive allows unarchiving a thread that isn't archived

If idempotency is intended, this is fine. Otherwise, consider enforcing valid state transitions:

♻️ Suggested enforcement of archive-state invariants
+import {
+  requireProject,
+  requireProjectAbsent,
+  requireThread,
+  requireThreadAbsent,
+  requireThreadNotArchived,
+  requireThreadArchived,
+} from "./commandInvariants.ts";

 case "thread.archive": {
-  yield* requireThread({
+  yield* requireThreadNotArchived({
     readModel,
     command,
     threadId: command.threadId,
   });
   // ...
 }

 case "thread.unarchive": {
-  yield* requireThread({
+  yield* requireThreadArchived({
     readModel,
     command,
     threadId: command.threadId,
   });
   // ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/orchestration/decider.ts` around lines 193 - 236, The
archive/unarchive handlers allow invalid state transitions because they call
requireThread but not the new invariants; update the "thread.archive" case to
call requireThreadArchived? no — call requireThreadNotArchived({ readModel,
command, threadId: command.threadId }) before creating the thread.archived
event, and update the "thread.unarchive" case to call requireThreadArchived({
readModel, command, threadId: command.threadId }) before creating the
thread.unarchived event (keep the existing requireThread call if still needed),
so the handlers use the requireThreadNotArchived and requireThreadArchived
helpers to enforce valid transitions.
apps/server/src/provider/Layers/ProviderRegistry.test.ts (1)

451-487: Assert a few snapshot fields here, not just provider order.

This only proves enumeration. It will still pass if the newly added probes regress on status, installed, or authStatus, which is the main behavior this PR is changing. A couple of representative snapshot assertions would make this test much harder to false-pass.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ProviderRegistry.test.ts` around lines 451 -
487, The test currently only asserts provider order; add assertions that verify
key snapshot fields (e.g., status, installed, authStatus) for a few
representative providers to catch regressions in probe behavior: after obtaining
providers via ProviderRegistry/registry.getProviders (from
providerRegistryLayer/ProviderRegistryLive), assert that the provider objects
for "codex", "claudeAgent", and one CLI-based provider like "geminiCli" have the
expected installed (boolean), status (e.g., "available" or "unavailable"), and
authStatus (e.g., "Logged in"/"Authenticated" or similar) values based on the
mockCommandSpawnerLayer responses; keep these as explicit equality checks
alongside the existing provider order assertion.
apps/server/src/os-jank.test.ts (1)

6-38: Add one test for Linux with missing SHELL.

Current coverage validates Linux with explicit SHELL and Windows no-op, but it skips the Linux default-shell path. Adding that case would lock in the intended fallback behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/os-jank.test.ts` around lines 6 - 38, Add a new unit test
that verifies fixPath falls back to the Linux default shell when SHELL is
absent: create an env without SHELL (only PATH), call fixPath with platform:
"linux" and a stubbed readPath that returns a new PATH string, then assert
readPath was called with the Linux default shell (e.g., "/bin/bash") and that
env.PATH was updated to the returned value; reference the fixPath function, the
readPath mock, and env object to locate where to add this test.
apps/server/src/git/Layers/CodexTextGeneration.test.ts (1)

361-379: Avoid asserting the exact truncation boundary here.

This makes the integration test depend on the current character budget of the shared title sanitizer rather than the wiring in CodexTextGeneration. A harmless tweak to the normalization heuristic will break this test even if the feature still works. I'd assert the important invariants here instead: single-line output, quotes removed, non-empty result, and within the sidebar limit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/git/Layers/CodexTextGeneration.test.ts` around lines 361 -
379, The test currently asserts an exact truncation string which couples it to
the sanitizer's character budget; instead, update the test around
TextGeneration.generateThreadTitle to assert invariants: that generated.title is
a single-line string (no newline characters), has surrounding quotes removed (no
leading/trailing quotes), is non-empty, and its length is <= the sidebar title
max length used by your UI (reference the same constant/limit used by the
sanitizer or UI) rather than an exact truncated text; keep the existing sample
input and only change the expect assertions in CodexTextGeneration.test.ts.
apps/server/integration/OrchestrationEngineHarness.integration.ts (1)

308-311: Make this TextGeneration fake type-complete.

The harness stub implements only generateBranchName and generateThreadTitle, but TextGenerationShape requires four methods: generateCommitMessage, generatePrContent, generateBranchName, and generateThreadTitle. The as unknown as TextGenerationShape cast suppresses this gap. If an integration path calls a missing method, it will fail at runtime instead of at type-check time. Implement all four methods or remove the unsafe cast.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/integration/OrchestrationEngineHarness.integration.ts` around
lines 308 - 311, The TextGeneration fake created via Layer.succeed (assigned to
textGenerationLayer) is incomplete and uses an unsafe cast to
TextGenerationShape; implement all required methods (generateCommitMessage and
generatePrContent in addition to generateBranchName and generateThreadTitle) on
the fake object so it fully matches TextGenerationShape, return appropriate
Effect.succeed(...) values for each method (mirroring the existing style) and
remove the "as unknown as TextGenerationShape" cast so TypeScript can verify
completeness.
apps/server/src/git/Layers/SessionTextGeneration.ts (1)

461-466: Note: Different field access pattern than other methods.

generateThreadTitle accesses input.modelSelection.provider and input.modelSelection.model, while the other methods (generateCommitMessage, generatePrContent, generateBranchName) access input.provider and input.model directly. This is correct based on the type definitions but creates an inconsistency in the API surface. Consider whether future refactoring should consolidate these patterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/git/Layers/SessionTextGeneration.ts` around lines 461 - 466,
The generateThreadTitle call is using input.modelSelection.provider and
input.modelSelection.model which is inconsistent with other methods that use
input.provider and input.model; update the generateThreadTitle implementation
(where runProviderJson is invoked) to use input.provider and input.model to
match generateCommitMessage/generatePrContent/generateBranchName, or
alternatively adjust the other methods and types to uniformly use modelSelection
— ensure the function name generateThreadTitle and the runProviderJson
invocation are updated to reference the chosen fields and update any affected
type definitions or callers to keep the API surface consistent.
apps/web/src/types.ts (1)

104-104: Consider making archivedAt non-optional if normalization guarantees presence.

If all thread objects are normalized to include archivedAt (string | null), changing this to a required field reduces undefined/null dual-state checks in UI code.

Suggested type tweak
-export interface Thread {
+export interface Thread {
   id: ThreadId;
   codexThreadId: string | null;
   projectId: ProjectId;
   title: string;
   modelSelection: ModelSelection;
   runtimeMode: RuntimeMode;
   interactionMode: ProviderInteractionMode;
   session: ThreadSession | null;
   messages: ChatMessage[];
   proposedPlans: ProposedPlan[];
   error: string | null;
   createdAt: string;
-  archivedAt?: string | null;
+  archivedAt: string | null;
   updatedAt?: string | undefined;
   latestTurn: OrchestrationLatestTurn | null;
   lastVisitedAt?: string | undefined;
   branch: string | null;
   worktreePath: string | null;
   turnDiffSummaries: TurnDiffSummary[];
   activities: OrchestrationThreadActivity[];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/types.ts` at line 104, The Thread type's archivedAt property is
declared optional (archivedAt?: string | null) but normalization guarantees it
exists; make archivedAt required by removing the optional marker (change to
archivedAt: string | null) in the interface/ type definition in types.ts (e.g.,
the Thread or relevant type that currently declares archivedAt) and run a quick
search for places that assumed undefined to update any checks to handle string |
null instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e2c0170-2382-48f7-bbef-d46823eebf19

📥 Commits

Reviewing files that changed from the base of the PR and between 5d57de4 and 270ca4e.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (121)
  • .gitattributes
  • .github/VOUCHED.td
  • .github/workflows/pr-size.yml
  • .gitignore
  • apps/desktop/package.json
  • apps/desktop/src/preload.ts
  • apps/desktop/src/syncShellEnvironment.test.ts
  • apps/desktop/src/syncShellEnvironment.ts
  • apps/server/integration/OrchestrationEngineHarness.integration.ts
  • apps/server/integration/orchestrationEngine.integration.test.ts
  • apps/server/package.json
  • apps/server/src/bootstrap.test.ts
  • apps/server/src/bootstrap.ts
  • apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts
  • apps/server/src/checkpointing/Layers/CheckpointStore.test.ts
  • apps/server/src/codexAppServerManager.test.ts
  • apps/server/src/codexAppServerManager.ts
  • apps/server/src/git/Layers/ClaudeTextGeneration.test.ts
  • apps/server/src/git/Layers/ClaudeTextGeneration.ts
  • apps/server/src/git/Layers/CodexTextGeneration.test.ts
  • apps/server/src/git/Layers/CodexTextGeneration.ts
  • apps/server/src/git/Layers/GitCore.test.ts
  • apps/server/src/git/Layers/GitCore.ts
  • apps/server/src/git/Layers/GitManager.test.ts
  • apps/server/src/git/Layers/RoutingTextGeneration.ts
  • apps/server/src/git/Layers/SessionTextGeneration.ts
  • apps/server/src/git/Prompts.test.ts
  • apps/server/src/git/Prompts.ts
  • apps/server/src/git/Services/GitCore.ts
  • apps/server/src/git/Services/TextGeneration.ts
  • apps/server/src/git/Utils.ts
  • apps/server/src/keybindings.test.ts
  • apps/server/src/keybindings.ts
  • apps/server/src/main.test.ts
  • apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
  • apps/server/src/orchestration/Layers/CheckpointReactor.ts
  • apps/server/src/orchestration/Layers/OrchestrationEngine.ts
  • apps/server/src/orchestration/Layers/ProjectionPipeline.ts
  • apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts
  • apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
  • apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts
  • apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
  • apps/server/src/orchestration/Schemas.ts
  • apps/server/src/orchestration/commandInvariants.test.ts
  • apps/server/src/orchestration/commandInvariants.ts
  • apps/server/src/orchestration/decider.ts
  • apps/server/src/orchestration/projector.test.ts
  • apps/server/src/orchestration/projector.ts
  • apps/server/src/os-jank.test.ts
  • apps/server/src/os-jank.ts
  • apps/server/src/persistence/Layers/ProjectionRepositories.test.ts
  • apps/server/src/persistence/Layers/ProjectionThreads.ts
  • apps/server/src/persistence/Migrations.ts
  • apps/server/src/persistence/Migrations/016_CanonicalizeModelSelections.test.ts
  • apps/server/src/persistence/Migrations/017_ProjectionThreadsArchivedAt.ts
  • apps/server/src/persistence/Migrations/018_ProjectionThreadsArchivedAtIndex.ts
  • apps/server/src/persistence/Services/ProjectionThreads.ts
  • apps/server/src/processRunner.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.test.ts
  • apps/server/src/provider/Layers/CodexAdapter.test.ts
  • apps/server/src/provider/Layers/CodexAdapter.ts
  • apps/server/src/provider/Layers/ProviderRegistry.test.ts
  • apps/server/src/provider/Layers/ProviderRegistry.ts
  • apps/server/src/provider/Layers/ProviderService.ts
  • apps/server/src/terminal/Layers/Manager.test.ts
  • apps/server/src/wsServer/pushBus.ts
  • apps/server/vitest.config.ts
  • apps/web/package.json
  • apps/web/src/appSettings.ts
  • apps/web/src/components/AppSidebarLayout.tsx
  • apps/web/src/components/ChatMarkdown.tsx
  • apps/web/src/components/ChatView.logic.ts
  • apps/web/src/components/ProjectFavicon.tsx
  • apps/web/src/components/Sidebar.logic.test.ts
  • apps/web/src/components/Sidebar.logic.ts
  • apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx
  • apps/web/src/components/chat/MessagesTimeline.test.tsx
  • apps/web/src/components/desktopUpdate.logic.test.ts
  • apps/web/src/components/desktopUpdate.logic.ts
  • apps/web/src/components/settings/SettingsPanels.tsx
  • apps/web/src/components/settings/SettingsSidebarNav.tsx
  • apps/web/src/components/sidebar/SidebarUpdatePill.tsx
  • apps/web/src/contextMenuFallback.ts
  • apps/web/src/hooks/useLocalStorage.ts
  • apps/web/src/hooks/useSettings.test.ts
  • apps/web/src/hooks/useSettings.ts
  • apps/web/src/hooks/useThreadActions.ts
  • apps/web/src/keybindings.ts
  • apps/web/src/lib/desktopUpdateReactQuery.test.ts
  • apps/web/src/lib/desktopUpdateReactQuery.ts
  • apps/web/src/lib/storage.ts
  • apps/web/src/modelSelection.ts
  • apps/web/src/routeTree.gen.ts
  • apps/web/src/routes/_chat.settings.tsx
  • apps/web/src/routes/settings.archived.tsx
  • apps/web/src/routes/settings.general.tsx
  • apps/web/src/routes/settings.tsx
  • apps/web/src/store.ts
  • apps/web/src/terminal-links.test.ts
  • apps/web/src/terminal-links.ts
  • apps/web/src/terminalStateStore.ts
  • apps/web/src/timestampFormat.ts
  • apps/web/src/truncateTitle.test.ts
  • apps/web/src/truncateTitle.ts
  • apps/web/src/types.ts
  • apps/web/src/worktreeCleanup.test.ts
  • docs/effect-fn-checklist.md
  • package.json
  • packages/contracts/package.json
  • packages/contracts/src/ipc.ts
  • packages/contracts/src/keybindings.test.ts
  • packages/contracts/src/keybindings.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/settings.test.ts
  • packages/contracts/src/settings.ts
  • packages/shared/src/DrainableWorker.ts
  • packages/shared/src/String.test.ts
  • packages/shared/src/String.ts
  • packages/shared/src/shell.ts
  • scripts/mock-update-server.ts
💤 Files with no reviewable changes (2)
  • apps/web/src/truncateTitle.test.ts
  • apps/web/src/routes/_chat.settings.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/server/src/provider/Layers/ProviderRegistry.ts (1)

260-272: ⚠️ Potential issue | 🟠 Major

Don't turn a missing CLI into an empty discovery result here.

The isCommandMissingCause(...) => [] fallback only works when a later version probe is guaranteed. cursor, opencode, and kilo do not provide resolveProbeBinaryPath, so a missing command on PATH skips runVersionCommand() entirely and falls through to buildReadySnapshot() with installed: true.

💡 Minimal fix
         }).pipe(
           Effect.catchCause((cause) => {
             const error = unwrapProbeError(Cause.squash(cause));
-            if (isCommandMissingCause(error)) {
+            if (isCommandMissingCause(error) && binaryPath) {
               return Effect.succeed<ReadonlyArray<{ slug: string; name: string }>>([]);
             }
             return Effect.failCause(cause);
           }),
         )

Also applies to: 366-380

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ProviderRegistry.ts` around lines 260 - 272,
The current catch in the discoveredModels flow treats a missing CLI
(isCommandMissingCause) as an empty discovery result which hides "command
missing" errors and leads to incorrect installed: true behavior downstream;
instead, remove the special-case that returns [] for isCommandMissingCause and
let the cause propagate (i.e., fail the effect) so
runVersionCommand()/resolveProbeBinaryPath can handle missing binaries
correctly; apply the same change to the other identical block around
runVersionCommand (the similar code at lines 366-380) and reference
discoveredModels, options.fetchDiscoveredModels, wrapProbeError,
unwrapProbeError, and isCommandMissingCause when making the change.
🧹 Nitpick comments (2)
apps/web/src/components/settings/SettingsPanels.tsx (2)

711-716: MutationObserver cleanup may fire after component unmount.

The observer callback calls scrollToEnd() which references the el element. If the component unmounts before the 2-second timeout, the callback may execute on a detached element. While this likely fails silently, disconnecting the observer on component unmount would be cleaner.

Consider moving this scroll logic to a useEffect with proper cleanup if this pattern is reused, or accept the current behavior as a minor nit since scrollTo on a detached element is a no-op.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/settings/SettingsPanels.tsx` around lines 711 - 716,
The MutationObserver created in SettingsPanels.tsx can fire after unmount and
call scrollToEnd() on a detached el; wrap this observer logic in a useEffect
tied to the component lifecycle (or the element ref) so you create the
MutationObserver inside useEffect, call observer.observe(el, { childList: true,
subtree: true }) there, and ensure you call observer.disconnect() in the effect
cleanup to prevent callbacks after unmount; reference the observer, scrollToEnd,
and the el/ref when moving the logic so disconnect is always executed.

406-413: Prefer async native dialog over synchronous window.confirm.

Line 408 uses the synchronous window.confirm() for update installation confirmation, while restoreDefaults (line 544) uses the async native dialog API. Consider using the native API consistently for a better user experience on desktop.

♻️ Suggested refactor to use native dialog
-      const confirmed = window.confirm(
-        getDesktopUpdateInstallConfirmationMessage(
-          updateState ?? { availableVersion: null, downloadedVersion: null },
-        ),
-      );
+      const api = readNativeApi();
+      const message = getDesktopUpdateInstallConfirmationMessage(
+        updateState ?? { availableVersion: null, downloadedVersion: null },
+      );
+      const confirmed = await (api ?? { dialogs: { confirm: (msg: string) => Promise.resolve(window.confirm(msg)) } }).dialogs.confirm(message);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/settings/SettingsPanels.tsx` around lines 406 - 413,
The synchronous window.confirm call inside the "install" branch (where action
=== "install") should be replaced with the app's async native dialog usage (the
same pattern used in restoreDefaults) to avoid blocking the renderer; call the
existing async dialog helper instead of window.confirm with the message produced
by getDesktopUpdateInstallConfirmationMessage(updateState ?? { availableVersion:
null, downloadedVersion: null }), await its boolean result, and return early if
the user cancels; ensure the containing handler is marked async and update any
callsites so promise handling matches the pattern used elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/server/src/provider/Layers/ProviderRegistry.ts`:
- Around line 260-272: The current catch in the discoveredModels flow treats a
missing CLI (isCommandMissingCause) as an empty discovery result which hides
"command missing" errors and leads to incorrect installed: true behavior
downstream; instead, remove the special-case that returns [] for
isCommandMissingCause and let the cause propagate (i.e., fail the effect) so
runVersionCommand()/resolveProbeBinaryPath can handle missing binaries
correctly; apply the same change to the other identical block around
runVersionCommand (the similar code at lines 366-380) and reference
discoveredModels, options.fetchDiscoveredModels, wrapProbeError,
unwrapProbeError, and isCommandMissingCause when making the change.

---

Nitpick comments:
In `@apps/web/src/components/settings/SettingsPanels.tsx`:
- Around line 711-716: The MutationObserver created in SettingsPanels.tsx can
fire after unmount and call scrollToEnd() on a detached el; wrap this observer
logic in a useEffect tied to the component lifecycle (or the element ref) so you
create the MutationObserver inside useEffect, call observer.observe(el, {
childList: true, subtree: true }) there, and ensure you call
observer.disconnect() in the effect cleanup to prevent callbacks after unmount;
reference the observer, scrollToEnd, and the el/ref when moving the logic so
disconnect is always executed.
- Around line 406-413: The synchronous window.confirm call inside the "install"
branch (where action === "install") should be replaced with the app's async
native dialog usage (the same pattern used in restoreDefaults) to avoid blocking
the renderer; call the existing async dialog helper instead of window.confirm
with the message produced by
getDesktopUpdateInstallConfirmationMessage(updateState ?? { availableVersion:
null, downloadedVersion: null }), await its boolean result, and return early if
the user cancels; ensure the containing handler is marked async and update any
callsites so promise handling matches the pattern used elsewhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e8cf77ed-d312-480b-a418-a8b534d85c60

📥 Commits

Reviewing files that changed from the base of the PR and between 270ca4e and 70baa7c.

📒 Files selected for processing (16)
  • .github/workflows/pr-size.yml
  • apps/server/src/orchestration/projector.ts
  • apps/server/src/persistence/NodeSqliteClient.ts
  • apps/server/src/provider/Layers/CopilotAdapter.ts
  • apps/server/src/provider/Layers/ProviderRegistry.test.ts
  • apps/server/src/provider/Layers/ProviderRegistry.ts
  • apps/server/src/serverSettings.test.ts
  • apps/web/src/appSettings.ts
  • apps/web/src/components/KeybindingsToast.browser.tsx
  • apps/web/src/components/ProjectFavicon.tsx
  • apps/web/src/components/settings/SettingsPanels.tsx
  • apps/web/src/components/sidebar/SidebarUpdatePill.tsx
  • apps/web/src/hooks/useSettings.test.ts
  • apps/web/src/hooks/useSettings.ts
  • apps/web/src/hooks/useThreadActions.ts
  • packages/contracts/src/settings.ts
✅ Files skipped from review due to trivial changes (3)
  • apps/web/src/components/KeybindingsToast.browser.tsx
  • apps/web/src/hooks/useSettings.test.ts
  • apps/server/src/serverSettings.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/web/src/hooks/useSettings.ts
  • apps/web/src/components/ProjectFavicon.tsx
  • apps/web/src/components/sidebar/SidebarUpdatePill.tsx
  • apps/server/src/provider/Layers/ProviderRegistry.test.ts
  • .github/workflows/pr-size.yml
  • apps/web/src/hooks/useThreadActions.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/provider/Layers/ProviderRegistry.ts (1)

398-401: ⚠️ Potential issue | 🟠 Major

Use a stable projection for provider comparison instead of raw objects.

buildServerProvider returns plain object literals, which don't have Effect's value-based equality semantics. Since every sync rebuilds the provider array with a fresh checkedAt timestamp, Equal.equals will always detect a difference even when provider state is actually unchanged. This causes unnecessary change publications on every periodic sync cycle.

Compare a stable projection of the provider state (excluding the timestamp) or implement Equal semantics for ServerProvider instead of comparing the raw objects directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ProviderRegistry.ts` around lines 398 - 401,
haveProvidersChanged currently compares raw ServerProvider objects with
Equal.equals, which always differs because buildServerProvider injects a fresh
checkedAt timestamp; change it to compare a stable projection (e.g., map
previousProviders and nextProviders to a shape excluding checkedAt and other
volatile fields) or implement value-based equality for ServerProvider, then run
Equal.equals on that projection. Locate haveProvidersChanged and update the
comparison to project each ServerProvider (created by buildServerProvider) to a
stable key/state (for example {id, name, config, status, ...} without checkedAt)
before calling Equal.equals, ensuring unchanged providers don't trigger diffs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/provider/Layers/ProviderRegistry.ts`:
- Around line 422-437: The merge logic in applyManagedProviderSnapshot (and the
similar block at 443-456) currently does Ref.get → compute mergedProviders →
Ref.set which is racy; replace both with a single atomic
Ref.modify/Ref.updateAndGet on providersRef so the merge is based on the latest
cache state. Inside the atomic update compute previousProviders from the
supplied current value, build mergedProviders using ALL_PROVIDERS (same mapping
logic that checks snapshot.provider vs candidate), set the ref to
mergedProviders and return both previousProviders and mergedProviders so you can
check haveProvidersChanged(previousProviders, mergedProviders) and then
conditionally PubSub.publish(changesPubSub, mergedProviders). Ensure you use the
same symbols: applyManagedProviderSnapshot, providersRef, snapshot,
ALL_PROVIDERS, haveProvidersChanged, and changesPubSub.

---

Outside diff comments:
In `@apps/server/src/provider/Layers/ProviderRegistry.ts`:
- Around line 398-401: haveProvidersChanged currently compares raw
ServerProvider objects with Equal.equals, which always differs because
buildServerProvider injects a fresh checkedAt timestamp; change it to compare a
stable projection (e.g., map previousProviders and nextProviders to a shape
excluding checkedAt and other volatile fields) or implement value-based equality
for ServerProvider, then run Equal.equals on that projection. Locate
haveProvidersChanged and update the comparison to project each ServerProvider
(created by buildServerProvider) to a stable key/state (for example {id, name,
config, status, ...} without checkedAt) before calling Equal.equals, ensuring
unchanged providers don't trigger diffs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4798fc0b-63af-4473-9b7f-8715a60b87d6

📥 Commits

Reviewing files that changed from the base of the PR and between 70baa7c and abc6ec8.

📒 Files selected for processing (3)
  • apps/server/src/provider/Layers/ProviderRegistry.test.ts
  • apps/server/src/provider/Layers/ProviderRegistry.ts
  • apps/web/src/components/settings/SettingsPanels.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/server/src/provider/Layers/ProviderRegistry.test.ts
  • apps/web/src/components/settings/SettingsPanels.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/provider/Layers/ProviderRegistry.test.ts`:
- Around line 633-637: Move the baseline capture to after the first invocation
of registry.getProviders so you compare the probe count between the first and
second calls: call yield* registry.getProviders once, set initialProbeCount =
probeCount, call yield* registry.getProviders again, then
assert.strictEqual(probeCount, initialProbeCount). This change touches the
probeCount variable and the registry.getProviders calls in the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35a16a89-0255-48b2-a3f4-f97a2d244c0e

📥 Commits

Reviewing files that changed from the base of the PR and between 6ea4f4e and b5916a7.

📒 Files selected for processing (4)
  • apps/server/src/orchestration/projector.ts
  • apps/server/src/persistence/NodeSqliteClient.ts
  • apps/server/src/provider/Layers/ProviderRegistry.test.ts
  • apps/server/src/provider/Layers/ProviderRegistry.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/server/src/persistence/NodeSqliteClient.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/server/src/orchestration/projector.ts
  • apps/server/src/provider/Layers/ProviderRegistry.ts

The published beta.42 changed SqlError to require a `reason: SqlErrorReason`
field instead of `{ cause, message }`. Use `classifySqliteError` to produce
the correct reason type from native SQLite errors.
Update assertion to match the current UI copy.
@aaditagrawal aaditagrawal merged commit d98a0d5 into main Mar 30, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ effective changed lines (test files excluded in mixed PRs). 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.