fix(desktop): wire generationTimeoutSec preference to in-flight aborts#100
fix(desktop): wire generationTimeoutSec preference to in-flight aborts#100
Conversation
There was a problem hiding this comment.
Findings
- [Major] Preferences read failure is silently downgraded to "no timeout" — this violates the "no silent fallbacks" constraint and can leave long-running LLM calls unbounded instead of surfacing a contextual error to users, evidence
apps/desktop/src/main/generation-ipc.ts:48
Suggested fix:try { timeoutSec = await readTimeoutSec(); } catch (err) { throw new CodesignError( `Failed to read generation timeout preference: ${err instanceof Error ? err.message : String(err)}`, 'PREFERENCES_READ_FAILED', ); }
Summary
- Review mode: initial
- 1 issue found in the new timeout wiring: a silent fallback on preferences-read errors.
- Not found in repo/docs:
docs/VISION.md,docs/PRINCIPLES.md(missing in this checkout), so constraint validation usedCLAUDE.md+ changed code.
Testing
- Not run (automation)
- Suggested: add an IPC-level test asserting
codesign:v1:generaterejects with contextualCodesignErrorwhen preferences read fails during timeout arming.
open-codesign Bot
| id, | ||
| message: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| return () => {}; |
There was a problem hiding this comment.
Silent fallback here (return () => {}) masks preferences read failures and disables timeout enforcement. Please throw a contextual CodesignError instead so the UI surfaces the failure.
8917adc to
ce374b6
Compare
|
Addressed Codex [Major]: prefs read failure no longer silently degrades to unbounded LLM calls.
Commit: ce374b6 |
There was a problem hiding this comment.
Findings
- [Major] Timer overflow can turn a large timeout into near-immediate abort —
setTimeoutin Node caps delay to a 32-bit signed int; whengenerationTimeoutSec * 1000exceeds that limit, the callback runs almost immediately, unexpectedly cancelling valid in-flight generations. Evidenceapps/desktop/src/main/generation-ipc.ts:65
Suggested fix:const timeoutMs = timeoutSec * 1000; const MAX_TIMEOUT_MS = 2_147_483_647; if (timeoutMs > MAX_TIMEOUT_MS) { throw new CodesignError( `generationTimeoutSec is too large (${timeoutSec}). Max supported is ${Math.floor(MAX_TIMEOUT_MS / 1000)} seconds.`, 'PREFERENCES_READ_FAIL', ); } const handle = setTimeout(() => { // existing timeout abort logic }, timeoutMs);
Summary
- Review mode: follow-up after new commits
- 1 issue found in the newly added timeout arming path: oversized timeout values can overflow timer delay and cancel requests immediately.
- Not found in repo/docs:
docs/VISION.md,docs/PRINCIPLES.md.
Testing
- Not run (automation)
- Suggested: add a Vitest case where
readTimeoutSecreturns a value above2_147_483and assert a contextualCodesignErroris thrown before scheduling.
open-codesign Bot
| 'GENERATION_TIMEOUT', | ||
| ), | ||
| ); | ||
| }, timeoutSec * 1000); |
There was a problem hiding this comment.
[Major] timeoutSec * 1000 can exceed Node timer max (2_147_483_647ms) and fire almost immediately, aborting generations unexpectedly.
Suggested fix:
const timeoutMs = timeoutSec * 1000;
const MAX_TIMEOUT_MS = 2_147_483_647;
if (timeoutMs > MAX_TIMEOUT_MS) {
throw new CodesignError(
`generationTimeoutSec is too large (${timeoutSec}). Max supported is ${Math.floor(MAX_TIMEOUT_MS / 1000)} seconds.`,
PREFERENCES_READ_FAIL,
);
}
const handle = setTimeout(() => {
// existing timeout abort logic
}, timeoutMs);The Settings → Advanced → "Generation timeout" preference was persisted and validated, but the value was never read by the generate IPC handler. Logs show generations running >4 minutes against a configured 120s limit because no AbortController timer was ever scheduled. Extract `armGenerationTimeout` into `generation-ipc.ts` and call it from both the `codesign:v1:generate` and legacy `codesign:generate` handlers. Reads prefs lazily per-request so Settings changes apply on the next generation without an app restart. Failures to read prefs degrade to a no-op (warn only) so a corrupt prefs file cannot block generation. Throws CodesignError with code GENERATION_TIMEOUT on abort so the renderer can distinguish user-cancel from timeout in toast copy later. Tests: 4 new vitest cases covering fire / clear / read-failure / non-finite values, all using fake timers. Signed-off-by: hqhq1025 <[email protected]>
When the preferences read throws, armGenerationTimeout previously fell back to "no timeout" — long LLM calls would run unbounded with no signal to the user. Rethrow as PREFERENCES_READ_FAIL so the IPC caller logs generate.fail and the renderer surfaces a toast. Move armTimeout inside the try block in index.ts so inFlight cleanup still runs if it throws.
Node's setTimeout max delay is 2^31-1 ms (~24.8 days). Larger values
overflow and the timer fires immediately, aborting generation instantly
when the user picks a long timeout.
Clamp the millisecond value to TIMEOUT_MAX_MS before scheduling, and
reject NaN/Infinity/negative values up front with a CodesignError
('PREFERENCES_INVALID_TIMEOUT') instead of silently disabling the
timeout. A value of 0 still means 'disabled'.
ce374b6 to
e891e93
Compare
|
Pushed e891e93: clamp generation timeout to Node's setTimeout int32 cap (2_147_483_647 ms ≈ 24.8 days). Without this, large Also tightened input validation: NaN / Infinity / negative values now throw Tests added in
|
There was a problem hiding this comment.
Findings
- No actionable issues found in the added/modified lines of this update.
Summary
- Review mode: follow-up after new commits
- Re-reviewed the latest head diff and follow-up compare context; no new correctness/security/constraint violations attributable to this patch were found.
- Residual risk/testing gap: timeout behavior is covered at helper-unit level, but there is still no end-to-end IPC/UI test asserting that
GENERATION_TIMEOUTis surfaced to the user path. - Not found in repo/docs:
docs/VISION.md,docs/PRINCIPLES.md.
Testing
- Not run (automation)
open-codesign Bot
Summary
armGenerationTimeoutintoapps/desktop/src/main/generation-ipc.tsand calls it from bothcodesign:v1:generateand the legacycodesign:generateshim. Reads prefs lazily per-request so Settings changes apply on the next generation without an app restart.CodesignError(code: 'GENERATION_TIMEOUT'). Failures to read prefs degrade to a no-op (warn only) so a corrupt prefs file cannot block generation. Non-finite / non-positive values are also no-op for safety.Files
apps/desktop/src/main/generation-ipc.ts— newarmGenerationTimeout(id, controller, readTimeoutSec, logger)helper.apps/desktop/src/main/index.ts— wiresarmTimeout(id, controller)into both generate handlers + clears infinally.apps/desktop/src/main/generation-ipc.test.ts— 4 new vitest cases (fire / clear / read-failure / non-finite) using fake timers.Principles checks
Test plan
pnpm --filter @open-codesign/desktop test— 300/300 pass.pnpm exec biome check apps/desktop/src/main/{index,generation-ipc,generation-ipc.test}.ts— clean (only pre-existing complexity warning on apply-comment handler).pnpm exec tsc --noEmit -p tsconfig.node.json— clean.