fix: validate invalid wiki timeout values

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/2f7def72-828c-419c-a5db-1bf1e2f10203
This commit is contained in:
copilot-swe-agent[bot] 2026-05-17 07:23:32 +00:00 committed by GitHub
parent d05602112e
commit f8981ed4ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 102 additions and 3 deletions

View file

@ -37,6 +37,15 @@ export interface WikiCommandOptions {
retries?: string;
}
function parsePositiveIntegerOption(value: string | undefined, flag: string): number | undefined {
if (value === undefined) return undefined;
const trimmed = value.trim();
if (!/^[1-9]\d*$/.test(trimmed)) {
throw new Error(`${flag} must be a positive integer`);
}
return parseInt(trimmed, 10);
}
/**
* Prompt the user for input via stdin.
*/
@ -127,6 +136,15 @@ export const wikiCommand = async (inputPath?: string, options?: WikiCommandOptio
return;
}
let timeoutSeconds: number | undefined;
try {
timeoutSeconds = parsePositiveIntegerOption(options?.timeout, '--timeout');
} catch (error) {
console.log(` Error: ${(error as Error).message}\n`);
process.exitCode = 1;
return;
}
// ── Resolve LLM config (with interactive fallback) ─────────────────
// Save any CLI overrides immediately
if (
@ -350,9 +368,8 @@ export const wikiCommand = async (inputPath?: string, options?: WikiCommandOptio
}
// ── Apply per-run overrides not saved to config ────────────────────
if (options?.timeout) {
const secs = parseInt(options.timeout, 10);
if (!isNaN(secs) && secs > 0) llmConfig.requestTimeoutMs = secs * 1000;
if (timeoutSeconds !== undefined) {
llmConfig.requestTimeoutMs = timeoutSeconds * 1000;
}
if (options?.retries) {
const n = parseInt(options.retries, 10);

View file

@ -264,6 +264,88 @@ describe('WikiGenerator --review mode', () => {
});
});
describe('wikiCommand --timeout validation', () => {
const originalExitCode = process.exitCode;
beforeEach(() => {
vi.resetModules();
process.exitCode = undefined;
});
afterEach(() => {
vi.restoreAllMocks();
vi.doUnmock('../../src/storage/git.js');
vi.doUnmock('../../src/storage/repo-manager.js');
vi.doUnmock('../../src/core/wiki/llm-client.js');
vi.doUnmock('../../src/core/wiki/generator.js');
vi.doUnmock('cli-progress');
process.exitCode = originalExitCode;
});
it.each(['0', '-1', 'abc'])(
'rejects invalid --timeout value %s before starting generation',
async (timeout) => {
const generatorCtor = vi.fn().mockImplementation(() => ({
run: vi.fn(),
}));
vi.doMock('../../src/storage/git.js', () => ({
getGitRoot: vi.fn(),
isGitRepo: vi.fn().mockReturnValue(true),
}));
vi.doMock('../../src/storage/repo-manager.js', () => ({
getStoragePaths: vi
.fn()
.mockReturnValue({ storagePath: '/tmp/wiki-storage', lbugPath: '/tmp/wiki-db' }),
loadMeta: vi.fn().mockResolvedValue({ createdAt: '2026-01-01T00:00:00Z' }),
loadCLIConfig: vi.fn().mockResolvedValue({
apiKey: 'sk-test',
baseUrl: 'https://api.openai.com/v1',
model: 'gpt-4o',
provider: 'openai',
}),
saveCLIConfig: vi.fn(),
}));
vi.doMock('../../src/core/wiki/llm-client.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../src/core/wiki/llm-client.js')>();
return {
...actual,
resolveLLMConfig: vi.fn().mockResolvedValue({
apiKey: 'sk-test',
baseUrl: 'https://api.openai.com/v1',
model: 'gpt-4o',
maxTokens: 16_384,
temperature: 0,
provider: 'openai',
}),
};
});
vi.doMock('../../src/core/wiki/generator.js', () => ({
WikiGenerator: generatorCtor,
}));
vi.doMock('cli-progress', () => ({
default: {
SingleBar: vi.fn().mockImplementation(() => ({
start: vi.fn(),
update: vi.fn(),
stop: vi.fn(),
})),
Presets: { shades_grey: {} },
},
}));
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const { wikiCommand } = await import('../../src/cli/wiki.js');
await wikiCommand('/tmp/repo', { timeout });
expect(process.exitCode).toBe(1);
expect(generatorCtor).not.toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalledWith(' Error: --timeout must be a positive integer\n');
},
);
});
// ─── CLI config round-trip with cursor provider ──────────────────────
describe('CLI config round-trip with cursor provider', () => {