fix(team): propagate managed runtime settings env

This commit is contained in:
777genius 2026-05-27 18:56:24 +03:00
parent 5403a2cea9
commit 77e08af03f
7 changed files with 51 additions and 0 deletions

View file

@ -1252,6 +1252,7 @@ const STALL_CHECK_INTERVAL_MS = 10_000;
const STALL_WARNING_THRESHOLD_MS = 20_000;
const APP_TEAM_RUNTIME_DISALLOWED_TOOLS =
'TeamDelete,TodoWrite,TaskCreate,TaskUpdate,mcp__agent-teams__team_launch,mcp__agent-teams__team_stop';
const CLAUDE_TEAM_RUNTIME_SETTINGS_PATH_ENV = 'CLAUDE_TEAM_RUNTIME_SETTINGS_PATH';
const TEAM_JSON_READ_TIMEOUT_MS = 5_000;
const TEAM_CONFIG_MAX_BYTES = 10 * 1024 * 1024;
const TEAM_INBOX_MAX_BYTES = 2 * 1024 * 1024;
@ -2216,6 +2217,17 @@ function buildCodexCrossProviderSafeEnvPatch(env: NodeJS.ProcessEnv): NodeJS.Pro
return envPatch;
}
function applyAppManagedRuntimeSettingsPathEnv(
env: NodeJS.ProcessEnv,
settingsPath: string | null
): void {
if (settingsPath) {
env[CLAUDE_TEAM_RUNTIME_SETTINGS_PATH_ENV] = settingsPath;
} else {
delete env[CLAUDE_TEAM_RUNTIME_SETTINGS_PATH_ENV];
}
}
interface TeamRuntimeAuthContext {
teamName?: string;
authMaterialId?: string;
@ -2238,6 +2250,7 @@ interface TeamRuntimeLaunchArgsPlan {
providerArgs: string[];
extraArgs: string[];
inheritedProviderArgs: string[];
appManagedSettingsPath: string | null;
}
type WorkspaceTrustProviderArgsResolver = (input: {
@ -4259,6 +4272,7 @@ export class TeamProvisioningService {
providerArgs: rawProviderArgs,
extraArgs: rawExtraArgs,
inheritedProviderArgs: rawInheritedProviderArgs,
appManagedSettingsPath: null,
};
}
@ -4313,6 +4327,7 @@ export class TeamProvisioningService {
providerArgs: splitProviderArgs.passthroughArgs,
extraArgs: splitExtraArgs.passthroughArgs,
inheritedProviderArgs: splitInheritedArgs.passthroughArgs,
appManagedSettingsPath: settingsBundle?.settingsPath ?? null,
};
}
@ -14819,6 +14834,10 @@ export class TeamProvisioningService {
includeAnthropicHelper: providerId === 'anthropic',
contextLabel: `Direct teammate restart (${input.configuredMember.name})`,
});
applyAppManagedRuntimeSettingsPathEnv(
provisioningEnv.env,
runtimeArgsPlan.appManagedSettingsPath
);
const runtimeArgs = mergeJsonSettingsArgs([
'--agent-id',
@ -15010,6 +15029,10 @@ export class TeamProvisioningService {
includeAnthropicHelper: providerId === 'anthropic',
contextLabel: `Direct process teammate restart (${input.configuredMember.name})`,
});
applyAppManagedRuntimeSettingsPathEnv(
provisioningEnv.env,
runtimeArgsPlan.appManagedSettingsPath
);
const runtimeArgs = mergeJsonSettingsArgs([
'--teammate-runtime',
@ -20257,6 +20280,7 @@ export class TeamProvisioningService {
...runtimeArgsPlan.settingsArgs,
...runtimeArgsPlan.inheritedProviderArgs,
]);
applyAppManagedRuntimeSettingsPathEnv(shellEnv, runtimeArgsPlan.appManagedSettingsPath);
const runtimeWarning = buildRuntimeLaunchWarning(request, shellEnv, {
geminiRuntimeAuth,
promptSize,
@ -21575,6 +21599,7 @@ export class TeamProvisioningService {
emitProvisioningCheckpoint(run, 'Resolving cross-provider member launch args');
launchArgs.push(...runtimeArgsPlan.inheritedProviderArgs);
const finalLaunchArgs = mergeJsonSettingsArgs(launchArgs);
applyAppManagedRuntimeSettingsPathEnv(shellEnv, runtimeArgsPlan.appManagedSettingsPath);
const runtimeWarning = buildRuntimeLaunchWarning(request, shellEnv, {
geminiRuntimeAuth,
promptSize,

View file

@ -12,6 +12,7 @@ import type { TeamProviderId } from '@shared/types';
const DIRECT_TMUX_RESTART_ENV_KEYS = [
'CLAUDE_CONFIG_DIR',
'CLAUDE_TEAM_CONTROL_URL',
'CLAUDE_TEAM_RUNTIME_SETTINGS_PATH',
'CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST',
'CLAUDE_CODE_USE_OPENAI',
'CLAUDE_CODE_USE_BEDROCK',

View file

@ -62,6 +62,7 @@ describe('TeamProvisioningDirectRestart', () => {
CLAUDE_CODE_ENTRY_PROVIDER: 'gemini',
CLAUDE_CODE_CODEX_BACKEND: 'codex-native',
CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD: 'chatgpt',
CLAUDE_TEAM_RUNTIME_SETTINGS_PATH: '/tmp/runtime-settings.json',
},
'codex'
);
@ -74,6 +75,7 @@ describe('TeamProvisioningDirectRestart', () => {
expect(assignments).toContain("CLAUDE_CODE_ENTRY_PROVIDER='codex'");
expect(assignments).toContain("CLAUDE_CODE_CODEX_BACKEND='codex-native'");
expect(assignments).toContain("CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD='chatgpt'");
expect(assignments).toContain("CLAUDE_TEAM_RUNTIME_SETTINGS_PATH='/tmp/runtime-settings.json'");
expect(assignments).toContain("CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST='1'");
});

View file

@ -531,6 +531,7 @@ describe('TeamProvisioningService member MCP config safe e2e', () => {
settingsArgs: string[];
extraArgs: string[];
inheritedProviderArgs: string[];
appManagedSettingsPath: string | null;
}>;
}
).buildTeamRuntimeLaunchArgsPlan = vi.fn(async () => ({
@ -540,6 +541,7 @@ describe('TeamProvisioningService member MCP config safe e2e', () => {
settingsArgs: [],
extraArgs: [],
inheritedProviderArgs: [],
appManagedSettingsPath: null,
}));
(
svc as unknown as { updateDirectTmuxRestartMemberConfig: () => Promise<void> }

View file

@ -15538,6 +15538,7 @@ describe('TeamProvisioningService', () => {
settingsArgs: [],
extraArgs: [],
inheritedProviderArgs: [],
appManagedSettingsPath: null,
}));
(svc as any).materializeDirectProcessNativeBootstrapContext = vi.fn(async () => ({}));
(svc as any).updateDirectTmuxRestartMemberConfig = vi.fn(async () => {});
@ -15637,6 +15638,7 @@ describe('TeamProvisioningService', () => {
settingsArgs: [],
extraArgs: [],
inheritedProviderArgs: [],
appManagedSettingsPath: null,
}));
(svc as any).materializeDirectProcessNativeBootstrapContext = vi.fn(async () => ({}));
(svc as any).updateDirectTmuxRestartMemberConfig = vi.fn(async () => {});

View file

@ -3566,6 +3566,7 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
expect(result.settingsArgs[0]).toBe('--settings');
expect(result.inheritedProviderArgs).toEqual([]);
expect(result.appManagedSettingsPath).toBe(result.settingsArgs[1]);
const settings = JSON.parse(fs.readFileSync(result.settingsArgs[1], 'utf8'));
expect(settings.codex.forced_login_method).toBe('chatgpt');
});
@ -3651,6 +3652,7 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
expect(result.settingsArgs).toEqual([]);
expect(result.inheritedProviderArgs).toEqual(inheritedProviderArgs);
expect(result.appManagedSettingsPath).toBeNull();
});
it('coalesces inherited JSON settings into Anthropic helper settings without keeping helper path args', async () => {
@ -3685,6 +3687,7 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
expect(result.settingsArgs[0]).toBe('--settings');
expect(result.settingsArgs[1]).toContain(helperDir);
expect(result.settingsArgs[1]).not.toBe(helperSettingsPath);
expect(result.appManagedSettingsPath).toBe(result.settingsArgs[1]);
const settings = JSON.parse(fs.readFileSync(result.settingsArgs[1], 'utf8'));
expect(settings.apiKeyHelper).toBe(`'${path.join(helperDir, 'helper.sh')}'`);
expect(settings.codex.forced_login_method).toBe('chatgpt');

View file

@ -178,6 +178,16 @@ function readRuntimeSettingsFromLaunchArgs(callIndex = 0): Record<string, unknow
return JSON.parse(fs.readFileSync(settingsValue, 'utf8')) as Record<string, unknown>;
}
function readRuntimeSettingsPathFromLaunchArgs(callIndex = 0): string {
const args = vi.mocked(spawnCli).mock.calls[callIndex]?.[1] as string[] | undefined;
const settingsFlagIndex = args?.indexOf('--settings') ?? -1;
const settingsValue = settingsFlagIndex >= 0 ? args?.[settingsFlagIndex + 1] : null;
if (!settingsValue || settingsValue.trim().startsWith('{')) {
throw new Error('Failed to extract runtime settings path from spawn args');
}
return settingsValue;
}
function registerNoopOpenCodeRuntimeAdapter(svc: TeamProvisioningService): void {
svc.setRuntimeAdapterRegistry(
new TeamRuntimeAdapterRegistry([
@ -564,6 +574,9 @@ describe('TeamProvisioningService prompt content (solo mode discipline)', () =>
expect(extractBootstrapSpec().members).toEqual([
expect.objectContaining({ name: 'alice', provider: 'codex' }),
]);
const settingsPath = readRuntimeSettingsPathFromLaunchArgs();
const launchEnv = vi.mocked(spawnCli).mock.calls[0]?.[2]?.env as NodeJS.ProcessEnv;
expect(launchEnv.CLAUDE_TEAM_RUNTIME_SETTINGS_PATH).toBe(settingsPath);
const settings = readRuntimeSettingsFromLaunchArgs();
expect((settings.codex as Record<string, unknown>).forced_login_method).toBe('chatgpt');
@ -1260,6 +1273,9 @@ describe('TeamProvisioningService prompt content (solo mode discipline)', () =>
expect(extractBootstrapSpec().members).toEqual([
expect.objectContaining({ name: 'alice', provider: 'codex' }),
]);
const settingsPath = readRuntimeSettingsPathFromLaunchArgs();
const launchEnv = vi.mocked(spawnCli).mock.calls[0]?.[2]?.env as NodeJS.ProcessEnv;
expect(launchEnv.CLAUDE_TEAM_RUNTIME_SETTINGS_PATH).toBe(settingsPath);
const settings = readRuntimeSettingsFromLaunchArgs();
expect((settings.codex as Record<string, unknown>).forced_login_method).toBe('chatgpt');