Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/__tests__/cli-init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,36 @@ describe('ag init --model flag', () => {
};
expect(config.defaultSessionEnv?.ANTHROPIC_DEFAULT_MODEL).toBe('gpt-5');
});

it('replaces existing admin key on --force (issue #3351)', async () => {
// First init creates a key
let stdin = new PassThrough();
let stdout = new CaptureStream();
let stderr = new CaptureStream();
let runPromise = runCli(['init', '--yes'], { stdin, stdout, stderr });
setImmediate(() => stdin.end());
let code = await runPromise;
expect(code).toBe(0);

// Read first token
const configPath = join(projectDir, '.aegis', 'config.yaml');
const config1 = parseYaml(readFileSync(configPath, 'utf-8')) as { clientAuthToken: string };
const token1 = config1.clientAuthToken;
expect(token1).toBeTruthy();

// Second init with --force should replace the key, not crash
stdin = new PassThrough();
stdout = new CaptureStream();
stderr = new CaptureStream();
runPromise = runCli(['init', '--yes', '--force'], { stdin, stdout, stderr });
setImmediate(() => stdin.end());
code = await runPromise;
expect(code).toBe(0);

// Verify new token is different
const config2 = parseYaml(readFileSync(configPath, 'utf-8')) as { clientAuthToken: string };
const token2 = config2.clientAuthToken;
expect(token2).toBeTruthy();
expect(token2).not.toBe(token1);
});
});
12 changes: 11 additions & 1 deletion src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ export async function handleInit(args: string[], io: CliIO): Promise<number> {
return 1;
}

let createAdminToken = !existingToken;
let createAdminToken = !existingToken || force;
let baseUrl = existingConfig?.baseUrl
? normalizeBaseUrl(existingConfig.baseUrl)
: getConfiguredBaseUrl(currentConfig);
Expand Down Expand Up @@ -664,6 +664,8 @@ export async function handleInit(args: string[], io: CliIO): Promise<number> {
return 0;
}

// Issue #3351: --yes --force should auto-overwrite without prompting
if (!yes || !force) {
const prompter = createPrompter(io);
try {
const overwrite = await promptBoolean(prompter, io, `Overwrite ${displayConfigPath}?`, false);
Expand All @@ -686,6 +688,7 @@ export async function handleInit(args: string[], io: CliIO): Promise<number> {
}
} finally {
prompter.close();
}
}
}

Expand All @@ -710,6 +713,13 @@ export async function handleInit(args: string[], io: CliIO): Promise<number> {
await authManager.load();

if (generatedTokenRequested) {
// Issue #3351: --force should replace existing key, not crash
if (force) {
const existingKey = authManager.listKeys().find(k => k.name === 'ag-init-admin');
if (existingKey) {
await authManager.revokeKey(existingKey.id);
}
}
const createdKey = await authManager.createKey('ag-init-admin', 100, undefined, 'admin');
authToken = createdKey.key;
createdKeyId = createdKey.id;
Expand Down
Loading