# Files changed · PR \#23890 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23890/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(providers\): allow a separate CLI path for updates](https://git.cafe/pingdotgg/t3code/pull/23890.md)

Version: 2

Head commit: f80a5b3c6b002f906d030d50816ccf4aafa3ef1c

Base commit: 52bb5c8c4e0908e4ef10428c12258be55b964f39

Comparison base: 11adcb0e25a04785f70d01c3f2a858889683007e

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/server/src/provider/Drivers/ClaudeDriver.ts

Status: modified; +3; −0

```diff
@@ -147,6 +147,9 @@
     const resolveMaintenance = yield* makeCachedProviderMaintenanceResolution(
       resolveProviderMaintenanceCapabilitiesEffect(UPDATE, {
         binaryPath: effectiveConfig.binaryPath,
+        maintenanceBinaryPath: config.maintenanceBinaryPath
+          ? expandHomePath(config.maintenanceBinaryPath, yield* HostProcess.HomeDirectory)
+          : null,
         env: processEnv,
       }).pipe(
         Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
```

## apps/server/src/provider/Drivers/CodexDriver.ts

Status: modified; +3; −0

```diff
@@ -169,6 +169,9 @@
         makeCodexMaintenanceResolver(homeLayout.sharedHomePath),
         {
           binaryPath: effectiveConfig.binaryPath,
+          maintenanceBinaryPath: config.maintenanceBinaryPath
+            ? expandHomePath(config.maintenanceBinaryPath, yield* HostProcess.HomeDirectory)
+            : null,
           env: processEnv,
         },
       ).pipe(
```

## docs/user/install.md

Status: modified; +5; −0

```diff
@@ -165,6 +165,11 @@
 compare against the version Homebrew offers, which can trail the npm release by
 a few hours.
 
+If Codex or Claude uses a launcher as its **Binary path**, set **Maintenance binary path**
+to the underlying CLI installation in web or desktop Settings. Sessions keep using the
+launcher; Update now uses the maintenance installation and still verifies the session
+binary's version afterward. Clear the maintenance path to update the session binary again.
+
 Add another provider instance for a separate account or configuration. Each
 instance can have its own environment variables, such as API keys or a custom
 base URL. Mark secret values as sensitive; after saving, T3 Code does not display
```

## packages/contracts/src/settings.ts

Status: modified; +20; −2

```diff
@@ -585,6 +585,15 @@
   );
 }
 
+const MaintenanceBinaryPath = Schema.optionalKey(TrimmedString).pipe(
+  Schema.annotateKey({
+    title: "Maintenance binary path",
+    description:
+      "Underlying CLI to update when Binary path is a launcher. Leave empty to update the session binary.",
+    providerSettingsForm: { clearWhenEmpty: "omit" },
+  }),
+);
+
 export const CodexSettings = makeProviderSettingsSchema(
   {
     setupMode: Schema.optionalKey(Schema.Literals(["managed", "existing"])).pipe(
@@ -601,6 +610,7 @@
         providerSettingsForm: { placeholder: "codex", clearWhenEmpty: "omit" },
       }),
     ),
+    maintenanceBinaryPath: MaintenanceBinaryPath,
     homePath: TrimmedString.pipe(
       Schema.withDecodingDefault(Effect.succeed("")),
       Schema.annotateKey({
@@ -637,7 +647,7 @@
     ),
   },
   {
-    order: ["binaryPath", "homePath", "shadowHomePath", "launchArgs"],
+    order: ["binaryPath", "maintenanceBinaryPath", "homePath", "shadowHomePath", "launchArgs"],
   },
 );
 export type CodexSettings = typeof CodexSettings.Type;
@@ -680,6 +690,7 @@
         providerSettingsForm: { placeholder: "claude", clearWhenEmpty: "omit" },
       }),
     ),
+    maintenanceBinaryPath: MaintenanceBinaryPath,
     homePath: TrimmedString.pipe(
       Schema.withDecodingDefault(Effect.succeed("")),
       Schema.annotateKey({
@@ -733,7 +744,14 @@
     ),
   },
   {
+    order: [
+      "binaryPath",
+      "maintenanceBinaryPath",
+      "homePath",
+      "autoCompactWindow",
+      "idleCompaction",
+      "launchArgs",
-    order: ["binaryPath", "homePath", "autoCompactWindow", "idleCompaction", "launchArgs"],
+    ],
   },
 );
 export type ClaudeSettings = typeof ClaudeSettings.Type;
```

## packages/provider-core/src/server/maintenanceResolver.test.ts

Status: modified; +27; −0

```diff
@@ -738,6 +738,33 @@
       }),
   );
 
+  it.effect.skipIf(windowsHost)(
+    "updates the explicit maintenance installation while keeping the runtime wrapper separate",
+    () =>
+      Effect.gen(function* () {
+        const tempDir = yield* makeTempDir("t3-wrapper-maintenance");
+        const wrapper = NodePath.join(tempDir, "agentfleet-launcher");
+        const nativePath = NodePath.join(tempDir, ".local", "bin", "native-package-tool");
+        writeExecutable(wrapper);
+        writeExecutable(nativePath);
+        const resolve = (maintenanceBinaryPath: string) =>
+          resolveProviderMaintenanceCapabilitiesEffect(nativePackageToolUpdate, {
+            binaryPath: wrapper,
+            maintenanceBinaryPath,
+            env: { PATH: "" },
+          }).pipe(
+            Effect.provideService(HostProcess.Platform, "darwin"),
+            Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn),
+          );
+        expect((yield* resolve(nativePath)).update).toMatchObject({
+          executable: nativePath,
+          args: ["update"],
+        });
+        expect((yield* resolve(NodePath.join(tempDir, "missing"))).update).toBeNull();
+        expect((yield* resolve(" ")).update?.executable).toBe(wrapper);
+      }),
+  );
+
   it.effect.skipIf(windowsHost)("switches to native updates and runs the resolved executable", () =>
     Effect.gen(function* () {
       const tempDir = yield* makeTempDir("t3-native-capabilities");
```

## packages/provider-core/src/server/maintenanceResolver.ts

Status: modified; +3; −1

```diff
@@ -735,10 +735,12 @@
   resolver: ProviderMaintenanceCapabilitiesResolver,
   options?: {
     readonly binaryPath?: string | null;
+    readonly maintenanceBinaryPath?: string | null;
     readonly env?: NodeJS.ProcessEnv;
   },
 ) {
-  const binaryPath = nonEmptyString(options?.binaryPath);
+  const binaryPath =
+    nonEmptyString(options?.maintenanceBinaryPath) ?? nonEmptyString(options?.binaryPath);
   if (!binaryPath) {
     return yield* resolver.resolve(null);
   }
```
