Skip to content

Merge upstream main and preserve Copilot provider support#51

Merged
zortos293 merged 17 commits intomainfrom
capy/merge-upstream-copilot
Apr 18, 2026
Merged

Merge upstream main and preserve Copilot provider support#51
zortos293 merged 17 commits intomainfrom
capy/merge-upstream-copilot

Conversation

@zortos293
Copy link
Copy Markdown
Owner

@zortos293 zortos293 commented Apr 17, 2026

This PR merges the latest upstream pingdotgg/t3code main into the fork while preserving all Copilot-specific provider integration, web UX, and fork-safe CI/release behavior.

Server Runtime & Provider Layer

  • Restored Copilot adapter in ProviderAdapterRegistry.ts default adapters array
  • Re-introduced CopilotProviderLive/CopilotProvider wiring in ProviderRegistry.ts with refresh/streamChanges
  • Updated providerStatusCache.ts provider cache IDs and quotaSnapshots hydration
  • Restored Copilot adapter layer in ProviderLayerLive in server.ts
  • Updated PROVIDER_ORDER in serverSettings.ts to include copilot
  • Fixed relative import extensions for node16/nodenext in Copilot provider files

Contracts & Shared Types

  • Extended orchestration.ts ProviderKind and added CopilotModelSelection union
  • Added CopilotModelOptions, Copilot defaults, aliases, and Claude Opus 4.7 alias in model.ts
  • Restored ServerProvider quotaSnapshots and ServerProviderModel billingMultiplier in server.ts
  • Retained copilot settings, custom models, homePath in settings.ts with upstream sidebar grouping
  • Rewrote shared/model.ts with Copilot normalization helpers compatible with upstream
  • Updated shared/serverSettings.ts applyServerSettingsPatch for discriminated model selection

Web Provider Model Picker & Copilot UX

  • Restored modelSelection.ts with copilot custom model config and createModelSelection helper
  • Restored composerProviderRegistry.tsx with copilot trait handling
  • Restored ProviderModelPicker.tsx with Copilot quota summary and billing multiplier UI
  • Fixed store.ts ProviderKind import and Schema.is usage
  • Added quotaSnapshots to ServerProvider test mocks in browser/test files

Release & CI Workflow Preservation

  • Updated release.yml with GitHub-hosted ubuntu-24.04 runners and removed npm CLI publish_cli
  • Maintained upstream build/release manifest improvements
  • Updated ci.yml to GitHub-hosted ubuntu-24.04 with upstream expectations

Type System & Discriminated Unions

  • Fixed composerDraftStore.ts Copilot reasoningEffort null/undefined casting
  • Replaced raw ModelSelection literals with createModelSelection() in ChatComposer.tsx, ChatView.tsx, SettingsPanels.tsx
  • Fixed store.ts normalizeModelSelection to handle Copilot provider in union types
  • Fixed input.tsx style function call for updated InputState shape

Test Compatibility & Import Fixes

  • Added missing resolve import in dev-runner.test.ts
  • Removed duplicate launchArgs from codex settings in KeybindingsToast.browser.tsx
  • Fixed duplicate functions in MessagesTimeline.tsx and restored itemType field
  • Fixed relative import paths for ServerProvider and CopilotAdapter shapes
  • Fixed ClaudeAgentEffort type-only import in ClaudeAdapter.ts

Open TC-022 TC-022

@zortos293 zortos293 added the capy Generated by capy.ai label Apr 17, 2026 — with Capy AI
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Important

Review skipped

Too many files!

This PR contains 249 files, which is 99 over the limit of 150.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e043ea26-ec92-413d-8c4d-427a117254bf

📥 Commits

Reviewing files that changed from the base of the PR and between 482cf48 and 0c2e587.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (249)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .gitignore
  • apps/desktop/package.json
  • apps/desktop/scripts/dev-electron.mjs
  • apps/desktop/scripts/electron-launcher.mjs
  • apps/desktop/scripts/smoke-test.mjs
  • apps/desktop/scripts/start-electron.mjs
  • apps/desktop/src/appBranding.test.ts
  • apps/desktop/src/appBranding.ts
  • apps/desktop/src/backendPort.test.ts
  • apps/desktop/src/backendReadiness.test.ts
  • apps/desktop/src/clientPersistence.test.ts
  • apps/desktop/src/clientPersistence.ts
  • apps/desktop/src/confirmDialog.test.ts
  • apps/desktop/src/desktopSettings.test.ts
  • apps/desktop/src/desktopSettings.ts
  • apps/desktop/src/main.ts
  • apps/desktop/src/runtimeArch.test.ts
  • apps/desktop/src/serverExposure.test.ts
  • apps/desktop/src/serverListeningDetector.test.ts
  • apps/desktop/src/syncShellEnvironment.test.ts
  • apps/desktop/src/syncShellEnvironment.ts
  • apps/desktop/src/updateChannels.test.ts
  • apps/desktop/src/updateChannels.ts
  • apps/desktop/src/updateMachine.test.ts
  • apps/desktop/src/updateMachine.ts
  • apps/desktop/src/updateState.test.ts
  • apps/desktop/tsconfig.json
  • apps/desktop/tsdown.config.ts
  • apps/server/package.json
  • apps/server/scripts/cli.ts
  • apps/server/src/auth/Layers/AuthControlPlane.test.ts
  • apps/server/src/auth/Layers/AuthControlPlane.ts
  • apps/server/src/auth/Services/AuthControlPlane.ts
  • apps/server/src/auth/utils.test.ts
  • apps/server/src/bin.ts
  • apps/server/src/bootstrap.test.ts
  • apps/server/src/cli-config.test.ts
  • apps/server/src/cli.test.ts
  • apps/server/src/cli.ts
  • apps/server/src/codexAppServerManager.test.ts
  • apps/server/src/codexAppServerManager.ts
  • apps/server/src/environment/Layers/ServerEnvironment.ts
  • apps/server/src/git/Layers/CodexTextGeneration.ts
  • apps/server/src/git/Layers/GitHubCli.test.ts
  • apps/server/src/git/Layers/GitHubCli.ts
  • apps/server/src/git/Layers/GitManager.test.ts
  • apps/server/src/git/Layers/GitManager.ts
  • apps/server/src/git/Layers/RoutingTextGeneration.ts
  • apps/server/src/git/Services/GitHubCli.ts
  • apps/server/src/git/Services/TextGeneration.ts
  • apps/server/src/http.ts
  • apps/server/src/keybindings.test.ts
  • apps/server/src/keybindings.ts
  • apps/server/src/observability/LocalFileTracer.ts
  • apps/server/src/open.test.ts
  • apps/server/src/open.ts
  • apps/server/src/orchestration/Layers/CheckpointReactor.ts
  • apps/server/src/orchestration/Layers/ProjectionPipeline.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
  • apps/server/src/orchestration/Normalizer.ts
  • apps/server/src/os-jank.test.ts
  • apps/server/src/os-jank.ts
  • apps/server/src/persistence/Migrations.ts
  • apps/server/src/persistence/Migrations/025_CleanupInvalidProjectionPendingApprovals.test.ts
  • apps/server/src/persistence/Migrations/025_CleanupInvalidProjectionPendingApprovals.ts
  • apps/server/src/processRunner.test.ts
  • apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts
  • apps/server/src/project/Layers/RepositoryIdentityResolver.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.test.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.ts
  • apps/server/src/provider/Layers/ClaudeProvider.test.ts
  • apps/server/src/provider/Layers/ClaudeProvider.ts
  • apps/server/src/provider/Layers/CodexAdapter.test.ts
  • apps/server/src/provider/Layers/CodexProvider.ts
  • apps/server/src/provider/Layers/CopilotAdapter.test.ts
  • apps/server/src/provider/Layers/CopilotProvider.ts
  • apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts
  • apps/server/src/provider/Layers/ProviderRegistry.test.ts
  • apps/server/src/provider/Layers/ProviderRegistry.ts
  • apps/server/src/provider/Layers/ProviderService.test.ts
  • apps/server/src/provider/Layers/ProviderService.ts
  • apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts
  • apps/server/src/provider/Layers/ProviderSessionDirectory.ts
  • apps/server/src/provider/Layers/ProviderSessionReaper.test.ts
  • apps/server/src/provider/Layers/ProviderSessionReaper.ts
  • apps/server/src/provider/Services/ClaudeProvider.ts
  • apps/server/src/provider/Services/CodexProvider.ts
  • apps/server/src/provider/Services/CopilotProvider.ts
  • apps/server/src/provider/Services/ProviderSessionDirectory.ts
  • apps/server/src/provider/Services/ProviderSessionReaper.ts
  • apps/server/src/provider/cliVersion.test.ts
  • apps/server/src/provider/cliVersion.ts
  • apps/server/src/provider/codexAppServer.ts
  • apps/server/src/provider/codexCliVersion.ts
  • apps/server/src/provider/makeManagedServerProvider.ts
  • apps/server/src/provider/providerSnapshot.ts
  • apps/server/src/provider/providerStatusCache.test.ts
  • apps/server/src/provider/providerStatusCache.ts
  • apps/server/src/server.test.ts
  • apps/server/src/server.ts
  • apps/server/src/serverLogger.ts
  • apps/server/src/serverRuntimeStartup.ts
  • apps/server/src/serverRuntimeState.ts
  • apps/server/src/serverSettings.test.ts
  • apps/server/src/serverSettings.ts
  • apps/server/src/startupAccess.test.ts
  • apps/server/src/startupAccess.ts
  • apps/server/src/telemetry/Identify.ts
  • apps/server/src/telemetry/Layers/AnalyticsService.ts
  • apps/server/src/terminal/Layers/BunPTY.ts
  • apps/server/src/terminal/Layers/Manager.test.ts
  • apps/server/src/terminal/Layers/Manager.ts
  • apps/server/src/terminal/Layers/NodePTY.test.ts
  • apps/server/src/terminal/Layers/NodePTY.ts
  • apps/server/src/terminal/Services/Manager.ts
  • apps/server/src/ws.ts
  • apps/server/tsconfig.json
  • apps/server/vitest.config.ts
  • apps/web/package.json
  • apps/web/src/chat-scroll.test.ts
  • apps/web/src/chat-scroll.ts
  • apps/web/src/clientPersistenceStorage.test.ts
  • apps/web/src/clientPersistenceStorage.ts
  • apps/web/src/components/ChatMarkdown.browser.tsx
  • apps/web/src/components/ChatMarkdown.test.tsx
  • apps/web/src/components/ChatMarkdown.tsx
  • apps/web/src/components/ChatView.browser.tsx
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/CommandPalette.logic.ts
  • apps/web/src/components/CommandPalette.tsx
  • apps/web/src/components/CommandPaletteResults.tsx
  • apps/web/src/components/ComposerPromptEditor.tsx
  • apps/web/src/components/KeybindingsToast.browser.tsx
  • apps/web/src/components/PlanSidebar.tsx
  • apps/web/src/components/RightPanelSheet.tsx
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/ThreadStatusIndicators.tsx
  • apps/web/src/components/chat/ChatComposer.tsx
  • apps/web/src/components/chat/MessagesTimeline.browser.tsx
  • apps/web/src/components/chat/MessagesTimeline.test.tsx
  • apps/web/src/components/chat/MessagesTimeline.tsx
  • apps/web/src/components/chat/ProviderModelPicker.browser.tsx
  • apps/web/src/components/chat/ProviderModelPicker.tsx
  • apps/web/src/components/chat/TraitsPicker.browser.tsx
  • apps/web/src/components/chat/TraitsPicker.tsx
  • apps/web/src/components/chat/composerProviderRegistry.test.tsx
  • apps/web/src/components/settings/SettingsPanels.tsx
  • apps/web/src/components/timelineHeight.test.ts
  • apps/web/src/components/timelineHeight.ts
  • apps/web/src/components/ui/input.tsx
  • apps/web/src/composerDraftStore.ts
  • apps/web/src/contextMenuFallback.test.ts
  • apps/web/src/contextMenuFallback.ts
  • apps/web/src/environmentGrouping.test.ts
  • apps/web/src/environments/runtime/service.test.ts
  • apps/web/src/environments/runtime/service.ts
  • apps/web/src/filePathDisplay.test.ts
  • apps/web/src/filePathDisplay.ts
  • apps/web/src/hooks/useHandleNewThread.ts
  • apps/web/src/hooks/useSettings.ts
  • apps/web/src/index.css
  • apps/web/src/localApi.test.ts
  • apps/web/src/logicalProject.ts
  • apps/web/src/markdown-links.test.ts
  • apps/web/src/markdown-links.ts
  • apps/web/src/modelSelection.ts
  • apps/web/src/rightPanelLayout.ts
  • apps/web/src/routes/__root.tsx
  • apps/web/src/routes/_chat.$environmentId.$threadId.tsx
  • apps/web/src/rpc/serverState.test.ts
  • apps/web/src/sidebarProjectGrouping.ts
  • apps/web/src/store.test.ts
  • apps/web/src/store.ts
  • apps/web/src/terminal-links.ts
  • apps/web/src/uiStateStore.test.ts
  • apps/web/src/uiStateStore.ts
  • apps/web/src/wsTransport.test.ts
  • apps/web/src/wsTransport.ts
  • apps/web/tsconfig.json
  • docs/observability.md
  • docs/release.md
  • package.json
  • packages/client-runtime/src/index.ts
  • packages/client-runtime/src/knownEnvironment.test.ts
  • packages/contracts/package.json
  • packages/contracts/src/auth.ts
  • packages/contracts/src/editor.ts
  • packages/contracts/src/environment.ts
  • packages/contracts/src/filesystem.ts
  • packages/contracts/src/git.test.ts
  • packages/contracts/src/git.ts
  • packages/contracts/src/index.ts
  • packages/contracts/src/ipc.ts
  • packages/contracts/src/keybindings.test.ts
  • packages/contracts/src/keybindings.ts
  • packages/contracts/src/model.ts
  • packages/contracts/src/orchestration.test.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/project.ts
  • packages/contracts/src/provider.test.ts
  • packages/contracts/src/provider.ts
  • packages/contracts/src/providerRuntime.test.ts
  • packages/contracts/src/providerRuntime.ts
  • packages/contracts/src/rpc.ts
  • packages/contracts/src/server.test.ts
  • packages/contracts/src/server.ts
  • packages/contracts/src/settings.test.ts
  • packages/contracts/src/settings.ts
  • packages/contracts/src/terminal.test.ts
  • packages/contracts/src/terminal.ts
  • packages/shared/package.json
  • packages/shared/src/DrainableWorker.test.ts
  • packages/shared/src/KeyedCoalescingWorker.test.ts
  • packages/shared/src/Net.test.ts
  • packages/shared/src/String.test.ts
  • packages/shared/src/cliArgs.test.ts
  • packages/shared/src/cliArgs.ts
  • packages/shared/src/git.test.ts
  • packages/shared/src/model.test.ts
  • packages/shared/src/model.ts
  • packages/shared/src/path.test.ts
  • packages/shared/src/qrCode.ts
  • packages/shared/src/searchRanking.test.ts
  • packages/shared/src/serverSettings.test.ts
  • packages/shared/src/serverSettings.ts
  • packages/shared/src/shell.test.ts
  • packages/shared/src/shell.ts
  • scripts/build-desktop-artifact.test.ts
  • scripts/build-desktop-artifact.ts
  • scripts/dev-runner.test.ts
  • scripts/dev-runner.ts
  • scripts/lib/build-target-arch.test.ts
  • scripts/lib/build-target-arch.ts
  • scripts/lib/update-manifest.ts
  • scripts/merge-mac-update-manifests.test.ts
  • scripts/merge-update-manifests.test.ts
  • scripts/merge-update-manifests.ts
  • scripts/mock-update-server.test.ts
  • scripts/mock-update-server.ts
  • scripts/release-smoke.ts
  • scripts/resolve-nightly-release.test.ts
  • scripts/resolve-nightly-release.ts
  • scripts/tsconfig.json
  • scripts/update-release-package-versions.test.ts
  • scripts/update-release-package-versions.ts
  • tsconfig.base.json

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 capy/merge-upstream-copilot

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated
Comment thread apps/server/src/provider/Layers/ClaudeAdapter.ts Outdated
Comment thread apps/web/src/components/chat/TraitsPicker.tsx
Comment thread packages/shared/src/model.ts Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread packages/shared/src/cliArgs.ts Outdated
Comment thread scripts/mock-update-server.ts Outdated
Comment thread apps/server/src/ws.ts Outdated
Comment thread packages/shared/src/model.ts
Comment thread apps/web/src/components/ChatMarkdown.tsx
Comment thread apps/web/src/components/chat/TraitsPicker.tsx
Comment thread packages/shared/src/cliArgs.ts Outdated
Comment thread packages/shared/src/shell.ts Outdated
Comment thread packages/shared/src/cliArgs.ts Outdated
Comment thread .github/workflows/release.yml
Comment thread packages/shared/src/model.ts
Comment thread apps/server/src/ws.ts
Comment thread scripts/mock-update-server.ts
Comment thread apps/server/src/provider/Layers/ClaudeProvider.ts
Comment thread apps/server/src/provider/Layers/ClaudeAdapter.ts Outdated
Comment thread scripts/build-desktop-artifact.ts Outdated
Comment thread apps/web/src/components/Sidebar.tsx Outdated
Comment thread apps/web/src/components/ChatMarkdown.tsx
Comment thread apps/server/src/serverSettings.ts Outdated
Comment thread packages/shared/src/serverSettings.ts Outdated
Comment thread apps/server/src/git/Services/TextGeneration.ts
Comment thread apps/web/src/components/Sidebar.tsx
);
const checkedAt = new Date().toISOString();
const models = providerModelsFromSettings(
const allModels = providerModelsFromSettings(
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.

[🟡 Medium] [🔵 Bug]

checkClaudeProviderStatus builds the model list for the pre-probe / probe-failed branches from the raw built-in set, and makePendingClaudeProvider still does the same. That leaves claude-opus-4-7 visible before a successful claude --version check even though the adapter now hard-fails that model whenever installedVersion is unknown.

// apps/server/src/provider/Layers/ClaudeProvider.ts
const allModels = providerModelsFromSettings(
  BUILT_IN_MODELS,
  PROVIDER,
  normalizeClaudeCustomModelsForVersion(claudeSettings.customModels, null),
  DEFAULT_CLAUDE_MODEL_CAPABILITIES,
);

I verified that the web consumes provider.models directly in @apps/web/src/providerModels.ts and falls back to the first built-in model in @apps/web/src/modelSelection.ts, so on the initial pending snapshot or after a transient --version timeout/non-zero exit the UI can default Claude to Opus 4.7 and then @apps/server/src/provider/Layers/ClaudeAdapter.ts rejects startSession/sendTurn with the new validation error. Filter built-ins through getBuiltInClaudeModelsForVersion(null) in the unknown-version and pending-snapshot branches as well, so the UI never offers a model the runtime cannot accept.

Comment thread packages/shared/src/cliArgs.ts
Comment thread packages/shared/src/cliArgs.ts
Comment thread packages/contracts/src/settings.ts
? parsed.updateChannel
: null;
const isLegacySettings = parsed.updateChannelConfiguredByUser === undefined;
const updateChannelConfiguredByUser = parsed.updateChannelConfiguredByUser === true;
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.

[🟡 Medium] [🔵 Bug]

Older desktop builds already exposed a user-facing stable/nightly selector and persisted only updateChannel; after this migration, any legacy nightly install with a saved {"updateChannel":"latest"} is treated as not user-configured and is rewritten to the nightly default on startup. That silently changes the user’s update track during upgrade even though they had previously opted back to stable. ```ts
// apps/desktop/src/desktopSettings.ts
const isLegacySettings = parsed.updateChannelConfiguredByUser === undefined;
const updateChannelConfiguredByUser =
parsed.updateChannelConfiguredByUser === true ||
(isLegacySettings && parsedUpdateChannel === "nightly");

updateChannel:
updateChannelConfiguredByUser && parsedUpdateChannel !== null
? parsedUpdateChannel
: defaultSettings.updateChannel,
``` Preserve the stored legacy updateChannel when it is present, or add a versioned migration that can distinguish “unset” from “explicit stable” instead of forcing all legacy `latest` values to `defaultSettings.updateChannel`.

// Handle --key=value syntax
const eqIndex = rest.indexOf("=");
if (eqIndex !== -1) {
flags[rest.slice(0, eqIndex)] = rest.slice(eqIndex + 1);
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.

[🟡 Medium] [🔵 Bug]

parseCliArgs drops the value for any non-boolean flag when the next token begins with --, even if that token came from a quoted string. The new desktop settings UI stores Claude launchArgs as free-form text and ClaudeAdapter passes parseCliArgs(claudeSettings.launchArgs).flags directly into the SDK, so inputs like --append-system-prompt "--project foo" are misparsed as two flags instead of one flag/value pair. That makes a documented launch-arg use case impossible and silently changes the CLI invocation. Preserve quote intent during tokenization or otherwise allow explicitly quoted --... tokens to remain values for the preceding flag.

// packages/shared/src/cliArgs.ts
const next = tokens[i + 1];
if (next !== undefined && !next.startsWith("--")) {
  flags[rest] = next;
  i++;
} else {

Comment thread .github/workflows/release.yml
type ChatAttachment,
type ModelSelection,
type ProviderKind,
} from "@t3tools/contracts";
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.

[🟡 Medium] [🔵 Bug]

This PR now makes textGenerationModelSelection.provider = "copilot" a real persisted/runtime path, but the server text-generation implementation still hard-fails any non-"codex" selection in CodexTextGeneration (generateCommitMessage/generatePrContent/generateBranchName/generateThreadTitle all return TextGenerationError("Invalid model selection.") when provider !== "codex"). The changed contract here advertises all ProviderKind values as valid for git text generation:

// apps/server/src/git/Services/TextGeneration.ts
import type { ChatAttachment, ModelSelection, ProviderKind } from "@t3tools/contracts";

/** Providers that support git text generation (commit messages, PR content, branch names). */
export type TextGenerationProvider = ProviderKind;

I verified that GitManager and ProviderCommandReactor both read settings.textGenerationModelSelection and pass it through unchanged, while the PR also adds Copilot persistence/selection plumbing in packages/shared/src/serverSettings.ts and SettingsPanels.tsx. As a result, selecting Copilot for git text generation now breaks commit message generation, PR title/body generation, worktree branch naming, and first-turn thread title generation. Fix by either routing provider: "copilot" to an implementation that accepts Copilot selections, or normalizing Copilot text-generation selections to a Codex-compatible selection before these server call sites invoke TextGeneration.

}
return mapping;
}, [orderedProjects]);
return buildPhysicalToLogicalProjectKeyMap({
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.

[🟡 Medium] [🔵 Bug]

projectOrder is now written with physical project keys in the runtime sync path (derivePhysicalProjectKey(project)) and drag-reorder path (member.physicalProjectKey), but the read/replay path still compares those saved ids against scoped project refs here:

// apps/web/src/components/Sidebar.tsx
return orderItemsByPreferredIds({
  items: projects,
  preferredIds: projectOrder,
  getId: (project) => scopedProjectKey(scopeProjectRef(project.environmentId, project.id)),
});

orderItemsByPreferredIds() only matches exact ids, so the preferred order is silently dropped after sync and the sidebar falls back to raw project enumeration. useHandleNewThread() still has the same scoped-key lookup, so defaultProjectRef for chat.new / command-palette new-thread actions also ignores the user’s manual sidebar order. Replaying the order with derivePhysicalProjectKey(project) in both read sites (or switching the writers back to scoped keys consistently) fixes the regression.

});
}

const existingContext = sessions.get(input.threadId);
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.

[🟠 High] [🔵 Bug]

startSession() now tears down an existing thread session before any of the new-session failure points run (getSettings, CLI version probe, Opus 4.7 validation, createQuery). If any of those steps fail, the thread is left with no active Claude session even though the old one was still usable, which regresses the repo's stated reliability requirement around session restarts/failures. Swap only after the replacement session has been successfully validated and created, or keep the old context alive until the new one is ready.

// apps/server/src/provider/Layers/ClaudeAdapter.ts
const existingContext = sessions.get(input.threadId);
if (existingContext) {
  yield* stopSessionInternal(existingContext, {
    emitExitEvent: false,
  }).pipe(

Comment on lines +3325 to +3328
Effect.map((result) =>
Option.isSome(result)
? parseGenericCliVersion(`${result.value.stdout}\n${result.value.stderr}`)
: null,
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.

[🟡 Medium] [🔵 Bug]

The adapter's private CLI probe parses a version string from stdout/stderr without checking CommandResult.code, so startSession()/sendTurn() can treat Opus 4.7 support as verified even when claude --version exited non-zero. checkClaudeProviderStatus() handles the same probe as an error, so the runtime and provider snapshot can disagree about whether 4.7 is allowed. Require a zero exit code before accepting the parsed version.

// apps/server/src/provider/Layers/ClaudeAdapter.ts
Effect.map((result) =>
  Option.isSome(result)
    ? parseGenericCliVersion(`${result.value.stdout}\n${result.value.stderr}`)
    : null,
),
Suggested change
Effect.map((result) =>
Option.isSome(result)
? parseGenericCliVersion(`${result.value.stdout}\n${result.value.stderr}`)
: null,
Effect.map((result) =>
Option.isSome(result) && result.value.code === 0
? parseGenericCliVersion(`${result.value.stdout}\n${result.value.stderr}`)
: null,
),

Comment thread apps/desktop/src/desktopSettings.ts Outdated
parsed.serverExposureMode === "network-accessible" ? "network-accessible" : "local-only",
updateChannel: parsed.updateChannel === "nightly" ? "nightly" : "latest",
updateChannel:
(!isLegacySettings && updateChannelConfiguredByUser && parsedUpdateChannel !== null) ||
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.

[🟡 Medium] [🔵 Bug]

Legacy settings files only persist updateChannel, so an older nightly user who explicitly switched to stable is serialized the same way as any other legacy file with "latest". This migration treats every legacy file whose stored channel differs from the current build default as implicit and falls back to defaultSettings.updateChannel, which flips that user back to nightly the first time a nightly build reads the file. That reintroduces the preference-loss bug instead of preserving user intent; the migration needs to carry forward the stored legacy channel or persist an explicit migration marker before rewriting it.

// apps/desktop/src/desktopSettings.ts
updateChannel:
  (!isLegacySettings && updateChannelConfiguredByUser && parsedUpdateChannel !== null) ||
  (isLegacySettings &&
    parsedUpdateChannel !== null &&
    parsedUpdateChannel === defaultSettings.updateChannel)

enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
binaryPath: makeBinaryPathSetting("claude"),
customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
launchArgs: Schema.String.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
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.

[🟡 Medium] [🔵 Bug]

This diff adds launchArgs to the authoritative ClaudeSettings schema, but the patch contract later in the same file still defines ClaudeSettingsPatch with only enabled, binaryPath, and customModels. The new settings UI at @apps/web/src/components/settings/SettingsPanels.tsx:1327 now sends providers.claudeAgent.launchArgs through updateSettings(), and @apps/web/src/hooks/useSettings.ts:165-168 optimistically applies that patch before sending it over the server.updateSettings RPC, whose payload is validated against ServerSettingsPatch. As a result, Claude launch-arg edits cannot round-trip through the authoritative server settings path and will be dropped or revert on the next config push.

// packages/contracts/src/settings.ts
export const ClaudeSettings = Schema.Struct({
  enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
  binaryPath: makeBinaryPathSetting("claude"),
  customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
  launchArgs: Schema.String.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
});


const trimmed = input.trim();
for (let index = 0; index < trimmed.length; index++) {
const char = trimmed[index]!;
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.

[🟡 Medium] [🔵 Bug]

parseCliArgs() now sits on the production path from the Claude settings textbox to ClaudeQueryOptions.extraArgs in @apps/server/src/provider/Layers/ClaudeAdapter.ts, but the tokenizer treats every backslash as an escape prefix instead of preserving non-whitespace backslashes. That means a value like --mcp-config C:\Users\alice\.claude.json is rewritten to C:Usersalice.claude.json before Claude is spawned, breaking Windows paths and any other flag values that legitimately contain backslashes. The new tests only cover escaped spaces, so this regression is currently unchecked. ts // packages/shared/src/cliArgs.ts if (char === "\\") { tokenStarted = true; escaping = true; continue; } Preserve \ unless it is actually escaping whitespace or a quote, and add a regression test for Windows-style path arguments passed through launchArgs.

for (let index = 0; index < trimmed.length; index++) {
const char = trimmed[index]!;
const nextChar = trimmed[index + 1];

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.

[🟡 Medium] [🔵 Bug]

@packages/shared/src/cliArgs.ts now tokenizes a raw settings string with a plain whitespace split before @apps/server/src/provider/Layers/ClaudeAdapter.ts forwards the parsed flags to the Claude SDK as extraArgs. That breaks any valid Claude flag whose value contains spaces. For example, entering --append-system-prompt "Prefer numbered lists" in the settings UI becomes ['--append-system-prompt', '"Prefer', 'numbered', 'lists"'], so the SDK receives only "Prefer and silently drops the rest of the prompt text. This turns a supported launch-argument configuration into corrupted runtime behavior. Use a shell-aware tokenizer here, or persist launch args as a pre-split argv array instead of reparsing a free-form command string.

// packages/shared/src/cliArgs.ts
const tokens =
  typeof args === "string" ? args.trim().split(/\s+/).filter(Boolean) : Array.from(args);

include:
- label: macOS arm64
runner: macos-14
runner: macos-26
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.

[🟠 High] [🔵 Bug]

The new release matrix hard-codes Blacksmith runner labels for the macOS and Linux jobs, but those labels are not standard GitHub-hosted runners. Blacksmith requires separate runner provisioning/app installation, and this fork is a personal repository (zortos293/t3code-copilot), so these jobs will sit in Waiting for a runner / fail to start and the release pipeline never reaches packaging. Use GitHub-hosted labels here or make the runner labels configurable for forks.

# .github/workflows/release.yml
- label: macOS arm64
  runner: blacksmith-12vcpu-macos-26
...
- label: Linux x64
  runner: blacksmith-32vcpu-ubuntu-2404

const selectionPatch = patch.textGenerationModelSelection;
const next = deepMerge(current, patch);
if (!selectionPatch || !shouldReplaceTextGenerationModelSelection(selectionPatch)) {
return next;
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.

[🟡 Medium] [🔵 Bug]

When textGenerationModelSelection.provider changes without an explicit model, this helper reuses the old model unconditionally:

// packages/shared/src/serverSettings.ts
const provider = selectionPatch.provider ?? current.textGenerationModelSelection.provider;
const model = selectionPatch.model ?? current.textGenerationModelSelection.model;
if (provider === "codex") {

ServerSettingsPatch explicitly allows provider-only patches, so switching from a Claude selection to { provider: "codex" } now produces { provider: "codex", model: "claude-sonnet-4-6" }. That survives ServerSettings decoding because model is only validated as a non-empty string, and the server later passes it straight through to git text generation (codex exec --model <modelSelection.model>). The result is a persisted settings state that routes to the Codex generator with a Claude-only slug and fails at runtime. Fix by defaulting model to DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER[nextProvider] whenever the provider changes and no model is supplied, or by rejecting provider-only patches for this field.

),
);
const resolvedNext = resolveTextGenerationProvider(next);
yield* writeSettingsAtomically(next);
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.

[🟡 Medium] [🔵 Bug]

resolveTextGenerationProvider() is documented as a read-time fallback so the stored preference survives temporary provider changes, but updateSettings() now persists the resolved fallback instead of the validated raw settings:

// apps/server/src/serverSettings.ts
const resolvedNext = resolveTextGenerationProvider(next);
yield* writeSettingsAtomically(resolvedNext);
yield* Cache.set(settingsCache, cacheKey, resolvedNext);
yield* emitChange(resolvedNext);

With this path, disabling the currently selected provider (or selecting Copilot, which is intentionally excluded from GIT_TEXT_GENERATION_PROVIDERS) permanently overwrites textGenerationModelSelection on disk/cache, so the previous provider/model/options cannot come back when the provider is re-enabled. Persist next and only apply resolveTextGenerationProvider() on the returned/read path; otherwise the service loses the preference it claims to preserve. Also mirror that fix in the layerTest implementation so tests keep the same invariant.

export function buildProjectUiSyncInputs(
projects: ReturnType<typeof selectProjectsAcrossEnvironments>,
) {
const projectGroupingSettings = getUnifiedSettingsSnapshot();
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.

[🟡 Medium] [🔵 Bug]

buildProjectUiSyncInputs() now derives sidebar IDs from the current grouping settings, but the runtime service only calls syncProjects() during shell snapshot/project events. Changing sidebarProjectGroupingMode/overrides (or hydrating persisted client settings) immediately makes Sidebar.tsx rebuild rows under new project.projectKey values, while useUiStateStore.projectOrder and projectExpandedById stay keyed by the old IDs until some later project event happens. That drops manual ordering and collapsed/expanded state for the current session, which is exactly the state this sync is supposed to preserve. The new dependency is introduced here:

// apps/web/src/environments/runtime/service.ts
const projectGroupingSettings = getUnifiedSettingsSnapshot();
const logicalProjectInputs = projects.map((project, index) => ({
  key: deriveLogicalProjectKeyFromSettings(project, projectGroupingSettings),
  logicalId: deriveLogicalProjectKeyFromSettings(project, projectGroupingSettings),

Add a settings-change resync path (for grouping mode and overrides) so project UI state is rebuilt as soon as the grouping key function changes, not only after the next orchestration update.

Comment on lines +55 to +57
return null;
}

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.

[🟡 Medium] [🔵 Bug]

readBrowserClientSettings() now turns any schema decode failure into null, which breaks upgrade compatibility for existing browser-only users who still have older partial snapshots in localStorage. hydrateClientSettings() only applies persisted values when getClientSettings() returns a truthy object, so these users now silently fall back to defaults and lose preferences such as sort/grouping/timestamp settings after upgrading. Restore the legacy fallback that re-parses the raw JSON and decodes it with defaults (or add an explicit migration path) instead of treating every decode miss as "no settings".

// apps/web/src/clientPersistenceStorage.ts
try {
  return getLocalStorageItem(CLIENT_SETTINGS_STORAGE_KEY, ClientSettingsSchema);
} catch {
  return null;
}

@zortos293 zortos293 merged commit 7025d7c into main Apr 18, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capy Generated by capy.ai size:XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant