From 943441d75c6734617a4ee55a154a892b8d60691c Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 19 Oct 2026 12:02:07 -0400 Subject: [PATCH 1/3] fix(observability): keep the T3 Code launch mark on Claude Code sessions Signed-off-by: Yordis Prieto --- .../Adapters/ClaudeAdapterV2.test.ts | 29 +++++ .../Adapters/ClaudeAdapterV2.testkit.ts | 5 +- .../Adapters/ClaudeAdapterV2.ts | 20 +++- .../src/provider/Drivers/ClaudeHome.test.ts | 106 ++++++++++++++++++ .../server/src/provider/Drivers/ClaudeHome.ts | 65 +++++++++++ ...rness-telemetry-says-t3code-launched-it.md | 5 +- docs/operations/observability.md | 3 + .../src/server/instanceEnvironment.ts | 46 +++++--- 8 files changed, 262 insertions(+), 17 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index 687d415d2ffc1..fc799870ea1de 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -229,6 +229,35 @@ describe("ClaudeAdapterV2 runtime query policy", () => { assert.include(options.settings, { showThinkingSummaries: true }); }); + it("adds the settings env without dropping other flag settings", () => { + const settingsEnv = { OTEL_RESOURCE_ATTRIBUTES: "org.name=Acme,t3code.launched=true" }; + const options = ClaudeAdapterV2.makeClaudeQueryOptions({ + modelSelection: CLAUDE_TEST_MODEL_SELECTION, + nativeThreadId: "settings-env-thread", + resume: false, + cwd: "/workspace", + sdkSettings: { env: { KEEP: "1" }, model: "kept" }, + settingsEnv, + }); + assert.deepEqual(options.settings, { + model: "kept", + env: { KEEP: "1", ...settingsEnv }, + showThinkingSummaries: true, + }); + }); + + it("leaves a settings string untouched", () => { + const options = ClaudeAdapterV2.makeClaudeQueryOptions({ + modelSelection: CLAUDE_TEST_MODEL_SELECTION, + nativeThreadId: "settings-env-thread", + resume: false, + cwd: "/workspace", + sdkSettings: "/etc/claude.json", + settingsEnv: { OTEL_RESOURCE_ATTRIBUTES: "t3code.launched=true" }, + }); + assert.equal(options.settings, "/etc/claude.json"); + }); + it("preserves an explicit omitted thinking display", () => { const options = ClaudeAdapterV2.makeClaudeQueryOptions({ modelSelection: CLAUDE_TEST_MODEL_SELECTION, diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts index ec3ec5b052461..d075630337926 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts @@ -1060,7 +1060,10 @@ function layerClaudeProviderAdapterRegistryReplay( configMap: { [ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID]: { driver: ClaudeAdapterV2.CLAUDE_PROVIDER, - environment: [{ name: "CLAUDE_CODE_TMPDIR", value: tmpRoot, sensitive: false }], + environment: [ + { name: "CLAUDE_CODE_TMPDIR", value: tmpRoot, sensitive: false }, + { name: "CLAUDE_CONFIG_DIR", value: tmpRoot, sensitive: false }, + ], }, }, }), diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 27dd796e7d797..695721a4f735e 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -123,6 +123,7 @@ import * as ServerConfig from "../../config.ts"; import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion"; import { signalProcessGroup } from "@t3tools/provider-core/server/processGroup"; import { + claudeSettingsLaunchedEnv, claudeSignedOutMessage, makeClaudeEnvironment, } from "../../provider/Drivers/ClaudeHome.ts"; @@ -1104,6 +1105,7 @@ export function makeClaudeQueryOptions(input: { readonly attachmentsDir?: string; readonly settings?: ClaudeSettings; readonly sdkSettings?: string | ClaudeSdkSettings; + readonly settingsEnv?: Readonly>; readonly environment?: NodeJS.ProcessEnv; readonly mcpServers?: ClaudeQueryOptions["mcpServers"]; readonly tools?: ClaudeAgentSdkQueryTools; @@ -1141,13 +1143,21 @@ export function makeClaudeQueryOptions(input: { : typeof input.sdkSettings === "object" && input.sdkSettings !== null ? ({ ...input.sdkSettings, ...selectionSettings } as ClaudeSdkSettings) : selectionSettings; - const effectiveQuerySettings = + const compactedQuerySettings = input.settings?.autoCompactWindow === undefined || input.settings.autoCompactWindow.length === 0 ? querySettings : ({ ...(typeof querySettings === "object" && querySettings !== null ? querySettings : {}), autoCompactWindow: Number(input.settings.autoCompactWindow), } as ClaudeSdkSettings); + // A settings string is a path or JSON the caller owns, so it is left alone. + const effectiveQuerySettings = + input.settingsEnv === undefined || typeof compactedQuerySettings === "string" + ? compactedQuerySettings + : ({ + ...compactedQuerySettings, + env: { ...compactedQuerySettings?.env, ...input.settingsEnv }, + } as ClaudeSdkSettings); const options: ClaudeAgentSdkQueryOptions = { model: compiledSelection.apiModelId, tools: claudeAgentSdkQueryToolsForSdk(selectedTools), @@ -8613,6 +8623,13 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( turnInput.nativeThreadHasTurns ?? turnInput.providerTurnOrdinal > 1; const shouldResume = resumeSessionAt !== undefined || openedWithResume || hasPersistedProviderTurn; + const settingsEnv = yield* claudeSettingsLaunchedEnv( + turnInput.runtimePolicy.cwd, + adapterOptions.environment, + ).pipe( + Effect.provideService(FileSystem.FileSystem, fileSystem), + Effect.provideService(Path.Path, path), + ); const queryOptions = makeClaudeQueryOptions({ modelSelection: turnInput.modelSelection, nativeThreadId, @@ -8622,6 +8639,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( attachmentsDir, settings: adapterOptions.settings, environment: adapterOptions.environment, + ...(settingsEnv === undefined ? {} : { settingsEnv }), tools: queryPolicy.tools ?? CLAUDE_CODE_PRESET_TOOLS, ...(mcpOverrides.allowedTools === undefined ? {} diff --git a/apps/server/src/provider/Drivers/ClaudeHome.test.ts b/apps/server/src/provider/Drivers/ClaudeHome.test.ts index fb1caf753f869..1fe1cc7d5a658 100644 --- a/apps/server/src/provider/Drivers/ClaudeHome.test.ts +++ b/apps/server/src/provider/Drivers/ClaudeHome.test.ts @@ -3,9 +3,13 @@ import * as NodeOS from "node:os"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { describe, expect, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; +import * as HostProcess from "@t3tools/shared/HostProcess"; + import { + claudeSettingsLaunchedEnv, claudeSignedOutMessage, makeClaudeCapabilitiesCacheKey, makeClaudeContinuationGroupKey, @@ -85,4 +89,106 @@ it.layer(NodeServices.layer)("ClaudeHome", (it) => { }), ); }); + + describe("settings launched env", () => { + const LAUNCHED = "t3code.launched=true"; + const setup = (files: { + readonly user?: string; + readonly project?: string; + readonly local?: string; + }) => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const root = yield* fileSystem.makeTempDirectoryScoped(); + const configDir = path.join(root, "config"); + const cwd = path.join(root, "workspace"); + yield* fileSystem.makeDirectory(configDir, { recursive: true }); + yield* fileSystem.makeDirectory(path.join(cwd, ".claude"), { recursive: true }); + if (files.user !== undefined) { + yield* fileSystem.writeFileString(path.join(configDir, "settings.json"), files.user); + } + if (files.project !== undefined) { + yield* fileSystem.writeFileString( + path.join(cwd, ".claude", "settings.json"), + files.project, + ); + } + if (files.local !== undefined) { + yield* fileSystem.writeFileString( + path.join(cwd, ".claude", "settings.local.json"), + files.local, + ); + } + return { cwd, environment: { CLAUDE_CONFIG_DIR: configDir } }; + }); + const settingsWith = (attributes: string) => + JSON.stringify({ env: { OTEL_RESOURCE_ATTRIBUTES: attributes } }); + + it.effect("extends the value of the highest precedence settings file", () => + Effect.gen(function* () { + const { cwd, environment } = yield* setup({ + user: settingsWith("from=user"), + project: settingsWith("from=project"), + local: settingsWith("from=local"), + }); + expect(yield* claudeSettingsLaunchedEnv(cwd, environment)).toEqual({ + OTEL_RESOURCE_ATTRIBUTES: `from=local,${LAUNCHED}`, + }); + const withoutLocal = yield* setup({ + user: settingsWith("from=user"), + project: settingsWith("from=project"), + }); + expect( + yield* claudeSettingsLaunchedEnv(withoutLocal.cwd, withoutLocal.environment), + ).toEqual({ OTEL_RESOURCE_ATTRIBUTES: `from=project,${LAUNCHED}` }); + }).pipe(Effect.scoped, Effect.provideService(HostProcess.HomeDirectory, "/home/ada")), + ); + + it.effect("reads the user file when there is no workspace", () => + Effect.gen(function* () { + const { environment } = yield* setup({ user: settingsWith("org.name=Acme") }); + expect(yield* claudeSettingsLaunchedEnv(null, environment)).toEqual({ + OTEL_RESOURCE_ATTRIBUTES: `org.name=Acme,${LAUNCHED}`, + }); + }).pipe(Effect.scoped, Effect.provideService(HostProcess.HomeDirectory, "/home/ada")), + ); + + it.effect("changes nothing when no settings file overrides the variable", () => + Effect.gen(function* () { + const none = yield* setup({}); + expect(yield* claudeSettingsLaunchedEnv(none.cwd, none.environment)).toBeUndefined(); + const unrelated = yield* setup({ user: JSON.stringify({ env: { OTHER: "1" } }) }); + expect( + yield* claudeSettingsLaunchedEnv(unrelated.cwd, unrelated.environment), + ).toBeUndefined(); + }).pipe(Effect.scoped, Effect.provideService(HostProcess.HomeDirectory, "/home/ada")), + ); + + it.effect("skips an unreadable file and keeps looking", () => + Effect.gen(function* () { + const { cwd, environment } = yield* setup({ + user: settingsWith("from=user"), + local: "{ not json", + }); + expect(yield* claudeSettingsLaunchedEnv(cwd, environment)).toEqual({ + OTEL_RESOURCE_ATTRIBUTES: `from=user,${LAUNCHED}`, + }); + }).pipe(Effect.scoped, Effect.provideService(HostProcess.HomeDirectory, "/home/ada")), + ); + + it.effect("respects a launched value the user chose", () => + Effect.gen(function* () { + const inFile = yield* setup({ user: settingsWith("t3code.launched=false") }); + expect(yield* claudeSettingsLaunchedEnv(inFile.cwd, inFile.environment)).toBeUndefined(); + const optedOut = yield* setup({ user: settingsWith("from=user") }); + expect( + yield* claudeSettingsLaunchedEnv(optedOut.cwd, { + ...optedOut.environment, + OTEL_RESOURCE_ATTRIBUTES: "t3code.launched=false", + }), + ).toBeUndefined(); + }).pipe(Effect.scoped, Effect.provideService(HostProcess.HomeDirectory, "/home/ada")), + ); + }); }); diff --git a/apps/server/src/provider/Drivers/ClaudeHome.ts b/apps/server/src/provider/Drivers/ClaudeHome.ts index 0c7908b0264ca..d3c575252c8b9 100644 --- a/apps/server/src/provider/Drivers/ClaudeHome.ts +++ b/apps/server/src/provider/Drivers/ClaudeHome.ts @@ -4,8 +4,14 @@ import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; +import { + RESOURCE_ATTRIBUTES, + userLaunchedAttribute, + withLaunchedAttribute, +} from "@t3tools/provider-core/server/instanceEnvironment"; import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion"; import * as HostProcess from "@t3tools/shared/HostProcess"; +import { fromLenientJson } from "@t3tools/shared/schemaJson"; const quotePath = Schema.encodeSync(Schema.fromJsonString(Schema.String)); @@ -27,6 +33,65 @@ export const resolveClaudeHomePath = Effect.fn("resolveClaudeHomePath")(function return path.resolve(path.join(home, ".claude")); }); +const decodeSettingsEnv = Schema.decodeUnknownEffect( + fromLenientJson( + Schema.Struct({ env: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)) }), + ), +); + +/** + * Claude Code copies the `env` of each settings file over its own environment + * at startup, so a user's `OTEL_RESOURCE_ATTRIBUTES` there replaces the one T3 + * Code launched it with and drops `t3code.launched`. Flag settings outrank those + * files, so this returns the `env` to pass there: the value the files would + * leave in effect, plus the attribute. It returns nothing when no file sets the + * variable, the file value already carries the attribute, or the launch + * environment holds a value the user chose, such as `false`. + */ +export const claudeSettingsLaunchedEnv = Effect.fn("claudeSettingsLaunchedEnv")(function* ( + cwd: string | null, + environment: NodeJS.ProcessEnv, +): Effect.fn.Return< + Readonly> | undefined, + never, + FileSystem.FileSystem | Path.Path +> { + const launched = userLaunchedAttribute(environment[RESOURCE_ATTRIBUTES] ?? ""); + if (launched !== undefined && launched !== "true") { + return undefined; + } + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const configDir = yield* resolveClaudeHomePath({ homePath: "" }, environment); + const settingsPaths = [ + ...(cwd === null + ? [] + : [ + path.join(cwd, ".claude", "settings.local.json"), + path.join(cwd, ".claude", "settings.json"), + ]), + path.join(configDir, "settings.json"), + ]; + for (const settingsPath of settingsPaths) { + const contents = yield* fileSystem + .readFileString(settingsPath) + .pipe(Effect.orElseSucceed(() => undefined)); + if (contents === undefined) { + continue; + } + const attributes = (yield* decodeSettingsEnv(contents).pipe( + Effect.orElseSucceed(() => undefined), + ))?.env?.[RESOURCE_ATTRIBUTES]; + if (typeof attributes !== "string") { + continue; + } + return userLaunchedAttribute(attributes) === undefined + ? { [RESOURCE_ATTRIBUTES]: withLaunchedAttribute(attributes) } + : undefined; + } + return undefined; +}); + export const makeClaudeEnvironment = Effect.fn("makeClaudeEnvironment")(function* ( config: Pick, baseEnv?: NodeJS.ProcessEnv, diff --git a/docs/fork/0031-harness-telemetry-says-t3code-launched-it.md b/docs/fork/0031-harness-telemetry-says-t3code-launched-it.md index 4e5fc5c061837..19ab219eda32d 100644 --- a/docs/fork/0031-harness-telemetry-says-t3code-launched-it.md +++ b/docs/fork/0031-harness-telemetry-says-t3code-launched-it.md @@ -8,8 +8,9 @@ - Filter the metrics, traces, and logs that Claude Code, Codex, or any other harness exports to the sessions T3 Code drove, with `t3code.launched=true`. -- Keep your own `OTEL_RESOURCE_ATTRIBUTES`, inherited or set on a provider - instance; the attribute is added after them, never in their place. +- Keep your own `OTEL_RESOURCE_ATTRIBUTES`, inherited, set on a provider + instance, or set in your Claude settings files; the attribute is added after + them, never in their place. - Set `t3code.launched=false` on a provider instance to leave its harness unmarked. diff --git a/docs/operations/observability.md b/docs/operations/observability.md index 73bdac77d42aa..d6bdb8c9aeb49 100644 --- a/docs/operations/observability.md +++ b/docs/operations/observability.md @@ -642,6 +642,9 @@ Every process T3 Code launches for a provider, such as Claude Code or Codex, inh A harness that exports its own telemetry reports it on every signal, so its sessions driven from T3 Code can be told apart from the ones started in a terminal. A value already set for `t3code.launched`, in the environment or a provider instance's variables, is kept, so `false` opts a harness out. +Claude Code copies the `env` of its settings files over the environment it starts with, so when +one of them sets `OTEL_RESOURCE_ATTRIBUTES` T3 Code passes the value those files would leave in +effect, plus the attribute, as flag settings, which outrank them. Integrated terminals do not add it, though they keep one already in those variables. If the OTLP URLs are unset, local tracing still works, metrics stay in-process only, and logs stay diff --git a/packages/provider-core/src/server/instanceEnvironment.ts b/packages/provider-core/src/server/instanceEnvironment.ts index 2cfec03392465..5e1356cdc4bd5 100644 --- a/packages/provider-core/src/server/instanceEnvironment.ts +++ b/packages/provider-core/src/server/instanceEnvironment.ts @@ -39,7 +39,7 @@ export function literalProviderInstanceEnvironment( return literals; } -const RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES"; +export const RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES"; /** * Marks the telemetry of every process T3 Code launches, so a harness such as @@ -48,26 +48,46 @@ const RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES"; */ const LAUNCHED_ATTRIBUTE = { key: "t3code.launched", value: "true" } as const; -const hasResourceAttribute = (attributes: string, key: string): boolean => - attributes.split(",").some((entry) => { - const name = entry.split("=", 1)[0]?.trim() ?? ""; +const resourceAttributeValue = (attributes: string, key: string): string | undefined => { + for (const entry of attributes.split(",")) { + const separator = entry.indexOf("="); + const rawName = (separator === -1 ? entry : entry.slice(0, separator)).trim(); + const rawValue = separator === -1 ? "" : entry.slice(separator + 1).trim(); try { - return decodeURIComponent(name) === key; + if (decodeURIComponent(rawName) === key) { + return decodeURIComponent(rawValue); + } } catch { - return name === key; + if (rawName === key) { + return rawValue; + } } - }); + } + return undefined; +}; + +/** + * The value the user set for `t3code.launched` in a resource attribute list, + * or `undefined` when the list does not carry it. + */ +export const userLaunchedAttribute = (attributes: string): string | undefined => + resourceAttributeValue(attributes, LAUNCHED_ATTRIBUTE.key); + +export const withLaunchedAttribute = (attributes: string): string => { + const trimmed = attributes.trim(); + if (userLaunchedAttribute(trimmed) !== undefined) { + return trimmed; + } + const entry = `${encodeURIComponent(LAUNCHED_ATTRIBUTE.key)}=${encodeURIComponent(LAUNCHED_ATTRIBUTE.value)}`; + return trimmed === "" ? entry : `${trimmed},${entry}`; +}; const withLaunchedByAttribute = (environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv => { const attributes = environment[RESOURCE_ATTRIBUTES]?.trim() ?? ""; - if (hasResourceAttribute(attributes, LAUNCHED_ATTRIBUTE.key)) { + if (userLaunchedAttribute(attributes) !== undefined) { return environment; } - const entry = `${encodeURIComponent(LAUNCHED_ATTRIBUTE.key)}=${encodeURIComponent(LAUNCHED_ATTRIBUTE.value)}`; - return { - ...environment, - [RESOURCE_ATTRIBUTES]: attributes === "" ? entry : `${attributes},${entry}`, - }; + return { ...environment, [RESOURCE_ATTRIBUTES]: withLaunchedAttribute(attributes) }; }; /** From 4870235893cad09659647b38ebbac8917ab667ad Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 19 Oct 2026 12:15:03 -0400 Subject: [PATCH 2/3] fix(observability): find a relative Claude config dir where Claude Code does Signed-off-by: Yordis Prieto --- .../src/provider/Drivers/ClaudeHome.test.ts | 16 ++++++++++++++++ apps/server/src/provider/Drivers/ClaudeHome.ts | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/server/src/provider/Drivers/ClaudeHome.test.ts b/apps/server/src/provider/Drivers/ClaudeHome.test.ts index 1fe1cc7d5a658..7dbb48a45cbcf 100644 --- a/apps/server/src/provider/Drivers/ClaudeHome.test.ts +++ b/apps/server/src/provider/Drivers/ClaudeHome.test.ts @@ -165,6 +165,22 @@ it.layer(NodeServices.layer)("ClaudeHome", (it) => { }).pipe(Effect.scoped, Effect.provideService(HostProcess.HomeDirectory, "/home/ada")), ); + it.effect("resolves a relative config dir against the workspace", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const { cwd } = yield* setup({}); + yield* fileSystem.makeDirectory(path.join(cwd, "claude-config"), { recursive: true }); + yield* fileSystem.writeFileString( + path.join(cwd, "claude-config", "settings.json"), + settingsWith("org.name=Acme"), + ); + expect( + yield* claudeSettingsLaunchedEnv(cwd, { CLAUDE_CONFIG_DIR: "claude-config" }), + ).toEqual({ OTEL_RESOURCE_ATTRIBUTES: `org.name=Acme,${LAUNCHED}` }); + }).pipe(Effect.scoped, Effect.provideService(HostProcess.HomeDirectory, "/home/ada")), + ); + it.effect("skips an unreadable file and keeps looking", () => Effect.gen(function* () { const { cwd, environment } = yield* setup({ diff --git a/apps/server/src/provider/Drivers/ClaudeHome.ts b/apps/server/src/provider/Drivers/ClaudeHome.ts index d3c575252c8b9..4a9fd19f30048 100644 --- a/apps/server/src/provider/Drivers/ClaudeHome.ts +++ b/apps/server/src/provider/Drivers/ClaudeHome.ts @@ -62,7 +62,11 @@ export const claudeSettingsLaunchedEnv = Effect.fn("claudeSettingsLaunchedEnv")( } const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; - const configDir = yield* resolveClaudeHomePath({ homePath: "" }, environment); + const inheritedConfigDir = environment.CLAUDE_CONFIG_DIR?.trim() ?? ""; + const configDir = + cwd !== null && inheritedConfigDir.length > 0 && !path.isAbsolute(inheritedConfigDir) + ? path.resolve(cwd, inheritedConfigDir) + : yield* resolveClaudeHomePath({ homePath: "" }, environment); const settingsPaths = [ ...(cwd === null ? [] From c06cd120915cd3a83619fea7cf1d67a618b985dc Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 19 Oct 2026 12:54:27 -0400 Subject: [PATCH 3/3] test(orchestration): keep replay runs off the developer's home directory Signed-off-by: Yordis Prieto --- .../orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts | 5 +---- .../src/orchestration-v2/testkit/DeterministicRuntime.ts | 6 ++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts index d075630337926..ec3ec5b052461 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts @@ -1060,10 +1060,7 @@ function layerClaudeProviderAdapterRegistryReplay( configMap: { [ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID]: { driver: ClaudeAdapterV2.CLAUDE_PROVIDER, - environment: [ - { name: "CLAUDE_CODE_TMPDIR", value: tmpRoot, sensitive: false }, - { name: "CLAUDE_CONFIG_DIR", value: tmpRoot, sensitive: false }, - ], + environment: [{ name: "CLAUDE_CODE_TMPDIR", value: tmpRoot, sensitive: false }], }, }, }), diff --git a/apps/server/src/orchestration-v2/testkit/DeterministicRuntime.ts b/apps/server/src/orchestration-v2/testkit/DeterministicRuntime.ts index 2c2187d64a7a3..67f3cdb2af2ab 100644 --- a/apps/server/src/orchestration-v2/testkit/DeterministicRuntime.ts +++ b/apps/server/src/orchestration-v2/testkit/DeterministicRuntime.ts @@ -1,7 +1,12 @@ +import * as NodeOS from "node:os"; + +import * as HostProcess from "@t3tools/shared/HostProcess"; import * as Effect from "effect/Effect"; import * as Random from "effect/Random"; import { TestClock } from "effect/testing"; +const DETERMINISTIC_HOME = `${NodeOS.tmpdir()}/t3-deterministic-home`; + export function provideDeterministicTestRuntime( effect: Effect.Effect, options: { readonly randomSeed?: number } = {}, @@ -9,5 +14,6 @@ export function provideDeterministicTestRuntime( return effect.pipe( Effect.provide(TestClock.layer()), Random.withSeed(options.randomSeed ?? 0x1234_5678), + Effect.provideService(HostProcess.HomeDirectory, DETERMINISTIC_HOME), ); }