| 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,6 +455,7 @@ const runClaudeCommand = Effect.fn("runClaudeCommand")(function* ( |
| 455 | claudeSettings: ClaudeSettings, | 455 | claudeSettings: ClaudeSettings, |
| 456 | args: ReadonlyArray<string>, | 456 | args: ReadonlyArray<string>, |
| 457 | environment?: NodeJS.ProcessEnv, | 457 | environment?: NodeJS.ProcessEnv, |
| 458 | cwd?: string, | ||
| 458 | ) { | 459 | ) { |
| 459 | const claudeEnvironment = yield* makeClaudeEnvironment(claudeSettings, environment); | 460 | const claudeEnvironment = yield* makeClaudeEnvironment(claudeSettings, environment); |
| 460 | const spawnCommand = yield* resolveSpawnCommand(claudeSettings.binaryPath, args, { | 461 | const spawnCommand = yield* resolveSpawnCommand(claudeSettings.binaryPath, args, { |
| ... | @@ -463,6 +464,7 @@ const runClaudeCommand = Effect.fn("runClaudeCommand")(function* ( | ... | @@ -463,6 +464,7 @@ const runClaudeCommand = Effect.fn("runClaudeCommand")(function* ( |
| 463 | const command = ChildProcess.make(spawnCommand.command, spawnCommand.args, { | 464 | const command = ChildProcess.make(spawnCommand.command, spawnCommand.args, { |
| 464 | env: claudeEnvironment, | 465 | env: claudeEnvironment, |
| 465 | shell: spawnCommand.shell, | 466 | shell: spawnCommand.shell, |
| 467 | ...(cwd ? { cwd } : {}), | ||
| 466 | }); | 468 | }); |
| 467 | return yield* spawnAndCollect(claudeSettings.binaryPath, command); | 469 | return yield* spawnAndCollect(claudeSettings.binaryPath, command); |
| 468 | }); | 470 | }); |
| ... | @@ -533,6 +535,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( | ... | @@ -533,6 +535,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( |
| 533 | claudeSettings, | 535 | claudeSettings, |
| 534 | ["--version"], | 536 | ["--version"], |
| 535 | resolvedEnvironment, | 537 | resolvedEnvironment, |
| 538 | cwd, | ||
| 536 | ).pipe(Effect.timeoutOption(DEFAULT_TIMEOUT_MS), Effect.result); | 539 | ).pipe(Effect.timeoutOption(DEFAULT_TIMEOUT_MS), Effect.result); |
| 537 | 540 | ||
| 538 | if (Result.isFailure(versionProbe)) { | 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,7 +123,11 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. |
| 123 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 123 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 124 | const fileSystem = yield* FileSystem.FileSystem; | 124 | const fileSystem = yield* FileSystem.FileSystem; |
| 125 | const path = yield* Path.Path; | 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 | const httpClient = yield* HttpClient.HttpClient; | 131 | const httpClient = yield* HttpClient.HttpClient; |
| 128 | const latestVersions = yield* ProviderLatestVersions.ProviderLatestVersions; | 132 | const latestVersions = yield* ProviderLatestVersions.ProviderLatestVersions; |
| 129 | const resetCreditCoordinator = yield* ResetCreditCoordinator.ResetCreditCoordinator; | 133 | const resetCreditCoordinator = yield* ResetCreditCoordinator.ResetCreditCoordinator; |
| ... | @@ -204,7 +208,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. | ... | @@ -204,7 +208,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. |
| 204 | timeToLive: CAPABILITIES_PROBE_TTL, | 208 | timeToLive: CAPABILITIES_PROBE_TTL, |
| 205 | lookup: () => | 209 | lookup: () => |
| 206 | Effect.all([ | 210 | Effect.all([ |
| 207 | probeClaudeCapabilities(effectiveConfig, processEnv, cwd), | 211 | probeClaudeCapabilities(effectiveConfig, processEnv, stateDir), |
| 208 | ClaudeResetCredits.readClaudeOrganizationId(accountConfigPath), | 212 | ClaudeResetCredits.readClaudeOrganizationId(accountConfigPath), |
| 209 | ]).pipe( | 213 | ]).pipe( |
| 210 | Effect.map(([capabilities, workspaceId]) => | 214 | Effect.map(([capabilities, workspaceId]) => |
| ... | @@ -216,7 +220,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. | ... | @@ -216,7 +220,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. |
| 216 | }); | 220 | }); |
| 217 | const capabilitiesCacheKey = yield* makeClaudeCapabilitiesCacheKey( | 221 | const capabilitiesCacheKey = yield* makeClaudeCapabilitiesCacheKey( |
| 218 | effectiveConfig, | 222 | effectiveConfig, |
| 219 | cwd, | 223 | stateDir, |
| 220 | processEnv, | 224 | processEnv, |
| 221 | ); | 225 | ); |
| 222 | 226 | ||
| ... | @@ -230,7 +234,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. | ... | @@ -230,7 +234,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv, Path. |
| 230 | effectiveConfig, | 234 | effectiveConfig, |
| 231 | () => Cache.get(capabilitiesProbeCache, capabilitiesCacheKey), | 235 | () => Cache.get(capabilitiesProbeCache, capabilitiesCacheKey), |
| 232 | processEnv, | 236 | processEnv, |
| 233 | cwd, | 237 | stateDir, |
| 234 | catalog, | 238 | catalog, |
| 235 | scopedLimitNames, | 239 | scopedLimitNames, |
| 236 | (version) => | 240 | (version) => |
apps/server/src/provider/ProviderInstanceRegistry.test.ts+34| ... | @@ -437,6 +437,40 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => { | ... | @@ -437,6 +437,40 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => { |
| 437 | }).pipe(Effect.provide(layerTest)), | 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 | const redeemClaudeReset = (claim: { result: string; usageFailsAfterClaim: boolean }) => | 474 | const redeemClaudeReset = (claim: { result: string; usageFailsAfterClaim: boolean }) => |
| 441 | Effect.gen(function* () { | 475 | Effect.gen(function* () { |
| 442 | const fs = yield* FileSystem.FileSystem; | 476 | const fs = yield* FileSystem.FileSystem; |
apps/server/src/provider/testing/ProviderInstanceRegistryLive.fixture.mjs+6| ... | @@ -1,6 +1,12 @@ | ... | @@ -1,6 +1,12 @@ |
| 1 | #!/usr/bin/env node | 1 | #!/usr/bin/env node |
| 2 | import * as NodeFS from "node:fs"; | 2 | import * as NodeFS from "node:fs"; |
| 3 | import * as NodeReadline from "node:readline"; | 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 | if (process.argv.includes("--version")) { | 10 | if (process.argv.includes("--version")) { |
| 5 | process.stdout.write("claude 2.1.219\n"); | 11 | process.stdout.write("claude 2.1.219\n"); |
| 6 | process.exit(0); | 12 | process.exit(0); |