| author | |
| committer | |
| log | 233e70b3b2b42f52f12a8ad48089f648a1cb143d |
| tree | 7610277f3a583b6fac9733d6b7b39df69728dc92 |
| parent | 19a67ef5211996a737d9f9fe608ab2c80f8a6714 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>4 files changed, 51 insertions(+), 4 deletions(-)
apps/server/src/provider/ClaudeProvider.ts+3| ... | ... | @@ -455,6 +455,7 @@ const runClaudeCommand = Effect.fn("runClaudeCommand")(function* ( |
| 455 | 455 | claudeSettings: ClaudeSettings, |
| 456 | 456 | args: ReadonlyArray<string>, |
| 457 | 457 | environment?: NodeJS.ProcessEnv, |
| 458 | cwd?: string, | |
| 458 | 459 | ) { |
| 459 | 460 | const claudeEnvironment = yield* makeClaudeEnvironment(claudeSettings, environment); |
| 460 | 461 | const spawnCommand = yield* resolveSpawnCommand(claudeSettings.binaryPath, args, { |
| ... | ... | @@ -463,6 +464,7 @@ const runClaudeCommand = Effect.fn("runClaudeCommand")(function* ( |
| 463 | 464 | const command = ChildProcess.make(spawnCommand.command, spawnCommand.args, { |
| 464 | 465 | env: claudeEnvironment, |
| 465 | 466 | shell: spawnCommand.shell, |
| 467 | ...(cwd ? { cwd } : {}), | |
| 466 | 468 | }); |
| 467 | 469 | return yield* spawnAndCollect(claudeSettings.binaryPath, command); |
| 468 | 470 | }); |
| ... | ... | @@ -533,6 +535,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( |
| 533 | 535 | claudeSettings, |
| 534 | 536 | ["--version"], |
| 535 | 537 | resolvedEnvironment, |
| 538 | cwd, | |
| 536 | 539 | ).pipe(Effect.timeoutOption(DEFAULT_TIMEOUT_MS), Effect.result); |
| 537 | 540 | |
| 538 | 541 | if (Result.isFailure(versionProbe)) { |
apps/server/src/provider/Drivers/ClaudeDriver.ts+8-4| ... | ... | @@ -123,7 +123,11 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. |
| 123 | 123 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 124 | 124 | const fileSystem = yield* FileSystem.FileSystem; |
| 125 | 125 | const path = yield* Path.Path; |
| 126 | const { cwd } = yield* ServerConfig.ServerConfig; | |
| 126 | // Instance-level probes are not tied to a project, so they run in the | |
| 127 | // server's own state dir. The server cwd is $HOME in packaged desktop | |
| 128 | // builds, and `claude` wrappers that scope themselves to their cwd refuse | |
| 129 | // to run there. Workspace probes use the thread cwd via `snapshotForCwd`. | |
| 130 | const { stateDir } = yield* ServerConfig.ServerConfig; | |
| 127 | 131 | const httpClient = yield* HttpClient.HttpClient; |
| 128 | 132 | const latestVersions = yield* ProviderLatestVersions.ProviderLatestVersions; |
| 129 | 133 | const resetCreditCoordinator = yield* ResetCreditCoordinator.ResetCreditCoordinator; |
| ... | ... | @@ -204,7 +208,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. |
| 204 | 208 | timeToLive: CAPABILITIES_PROBE_TTL, |
| 205 | 209 | lookup: () => |
| 206 | 210 | Effect.all([ |
| 207 | probeClaudeCapabilities(effectiveConfig, processEnv, cwd), | |
| 211 | probeClaudeCapabilities(effectiveConfig, processEnv, stateDir), | |
| 208 | 212 | ClaudeResetCredits.readClaudeOrganizationId(accountConfigPath), |
| 209 | 213 | ]).pipe( |
| 210 | 214 | Effect.map(([capabilities, workspaceId]) => |
| ... | ... | @@ -216,7 +220,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. |
| 216 | 220 | }); |
| 217 | 221 | const capabilitiesCacheKey = yield* makeClaudeCapabilitiesCacheKey( |
| 218 | 222 | effectiveConfig, |
| 219 | cwd, | |
| 223 | stateDir, | |
| 220 | 224 | processEnv, |
| 221 | 225 | ); |
| 222 | 226 | |
| ... | ... | @@ -230,7 +234,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. |
| 230 | 234 | effectiveConfig, |
| 231 | 235 | () => Cache.get(capabilitiesProbeCache, capabilitiesCacheKey), |
| 232 | 236 | processEnv, |
| 233 | cwd, | |
| 237 | stateDir, | |
| 234 | 238 | catalog, |
| 235 | 239 | scopedLimitNames, |
| 236 | 240 | (version) => |
apps/server/src/provider/ProviderInstanceRegistry.test.ts+34| ... | ... | @@ -437,6 +437,40 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => { |
| 437 | 437 | }).pipe(Effect.provide(layerTest)), |
| 438 | 438 | ); |
| 439 | 439 | |
| 440 | it.live("runs Claude instance probes in the server state dir, not the server cwd", () => | |
| 441 | Effect.gen(function* () { | |
| 442 | if (yield* HostProcess.isWindows) return; | |
| 443 | ||
| 444 | const fixtures = yield* makeTildeProviderFixtures(); | |
| 445 | const { stateDir } = yield* ServerConfig.ServerConfig; | |
| 446 | const claudeId = ProviderInstanceId.make("claude_state_dir"); | |
| 447 | const { registry } = yield* makeProviderInstanceRegistry<ClaudeDriverEnv>({ | |
| 448 | drivers: [ClaudeDriver], | |
| 449 | configMap: { | |
| 450 | [claudeId]: { | |
| 451 | driver: ProviderDriverKind.make("claudeAgent"), | |
| 452 | enabled: true, | |
| 453 | // Packaged desktop builds start the server in $HOME, where some | |
| 454 | // `claude` wrappers refuse to run. | |
| 455 | environment: [{ name: "T3_CLAUDE_REQUIRED_CWD", value: stateDir, sensitive: false }], | |
| 456 | config: makeClaudeConfig({ | |
| 457 | enabled: true, | |
| 458 | binaryPath: fixtures.claudeBinaryPath, | |
| 459 | homePath: fixtures.claudeHomePath, | |
| 460 | }), | |
| 461 | }, | |
| 462 | }, | |
| 463 | }); | |
| 464 | const claude = yield* registry.getInstance(claudeId); | |
| 465 | ||
| 466 | expect(yield* claude!.snapshot.refresh).toMatchObject({ | |
| 467 | status: "ready", | |
| 468 | version: "2.1.219", | |
| 469 | auth: { status: "authenticated" }, | |
| 470 | }); | |
| 471 | }).pipe(Effect.provide(layerTest)), | |
| 472 | ); | |
| 473 | ||
| 440 | 474 | const redeemClaudeReset = (claim: { result: string; usageFailsAfterClaim: boolean }) => |
| 441 | 475 | Effect.gen(function* () { |
| 442 | 476 | const fs = yield* FileSystem.FileSystem; |
apps/server/src/provider/testing/ProviderInstanceRegistryLive.fixture.mjs+6| ... | ... | @@ -1,6 +1,12 @@ |
| 1 | 1 | #!/usr/bin/env node |
| 2 | 2 | import * as NodeFS from "node:fs"; |
| 3 | 3 | import * as NodeReadline from "node:readline"; |
| 4 | // Opt-in: act like a `claude` wrapper that only runs inside one directory. | |
| 5 | const requiredCwd = process.env.T3_CLAUDE_REQUIRED_CWD; | |
| 6 | if (requiredCwd && process.cwd() !== NodeFS.realpathSync(requiredCwd)) { | |
| 7 | process.stderr.write(`claude: refusing to run outside ${requiredCwd}\n`); | |
| 8 | process.exit(1); | |
| 9 | } | |
| 4 | 10 | if (process.argv.includes("--version")) { |
| 5 | 11 | process.stdout.write("claude 2.1.219\n"); |
| 6 | 12 | process.exit(0); |