feat(ai,pulls,automations,activity): add dedicated security models and...
Brought to you by:
thebguy
Originally created by: theBGuy
Originally owned by: theBGuy
Add an optional security-audit model independent from the general review model, while preserving the existing review configuration as the fallback. Track review execution time across manual and automated runs so users can monitor active reviews and understand completed or stopped run durations.
securityReviewAi settings and centralized fallback logic in src/lib/settings/api.ts, keeping security audits on reviewAi unless a dedicated configuration is enabled.src/features/settings/AiProviderSection.tsx.src/features/pulls/PrReviewPanel.tsx, while preserving per-run in-panel overrides.src/lib/automations/runner.ts, including provider lane selection, generated comment labels, and persisted review history.README.md and src/features/help/content.ts.changelog.d/added-security-review-model.md.startedAt and endedAt lifecycle timestamps to review entries in src/lib/stores/reviews.ts, excluding queue time from manually queued run durations.src/components/elapsed-time.tsx and compact duration formatting in src/lib/time.ts.src/features/activity/ActivityDock.tsx.src/features/pulls/PrReviewPanel.tsx.src/features/pulls/ReviewHistory.tsx.src/lib/automations/runner.ts.changelog.d/added-review-runtimes.md and src/features/help/content.ts.
Originally posted by: cloudflare-workers-and-pages[bot]
Deploying gitdesktop with
Cloudflare Pages
6e90231View logs
Originally posted by: theBGuy
Notes for reviewers
Recorded decisions + evidence for this batch (two features: optional dedicated security-audit model, and review runtime display). Numbered so findings can reference them.
pnpm buildgreen; all scopedbiome checks clean; live E2E on the running app (item 12).securityReviewAiabsence semantics. Deliberately NOT inDEFAULT_SETTINGS;loadSettingsnested-merges only when present; toggle-OFF savesundefined, which JSON serialization drops. Live-verified on disk both directions: ON+Save → key present; OFF+Save → key absent. Discard after toggling ON reverts to absent (live-verified).effectiveReviewAi(settings, mode)returns the security config formode === "security"only. AI conflict resolution and CI-debug deliberately keepreviewAi(rule of three — per-mode models for those wait for real demand; please don't suggest generalizing now).startedAtis stamped at the RUNNING transition — queue wait is deliberately excluded from elapsed. The persisted history record'sstartedAtmoved to the same stamp, so records saved before this change measured from enqueue: historical durations mix two semantics (noted in a code comment in ReviewHistory). Old records with zero span show no duration by design (the> 0guard).ElapsedTimeclonesrelative-time.tsx's module-level shared-snapshot pattern at 1s (useSyncExternalStore; interval starts at 0→1 subscribers with an immediate refresh, cleared at 1→0). No bareDate.now()in any render path — this repo has a documented React Compiler freeze gotcha; please don't suggest per-rowsetIntervalor render-time clock reads.ElapsedTimespans. Store entries carry static stamps; nothing patches the store per tick, souseReviewTasksconsumers are untouched by ticking.startedAtand shows no "ran X" (deliberate — it never ran).persistReviewHistorypreviously wrotestartedAt: now, finishedAt: now— automation-delivered reviews could never show a history duration (that's why pre-batch records have none). Now threads the run's real start (runStartedMs, captured atregisterAutomationRun). Not yet exercised by a live automation delivery — the first automated review on this PR is the confirmation.formatDurationcontract: "42s" under a minute (floored), "3m 12s" under an hour (seconds unpadded), "1h 2m" above; non-finite/negative clamp to "0s". Edge cases walked: 59_999→"59s", 60_000→"1m 0s", 3_599_999→"59m 59s", 3_600_000→"1h 0m".{ ...reviewAi }at toggle-ON), not the persisted one — an unsaved review-model edit carries into the seed, which matches the batched Save/Discard form model.Posted by GitDesktop — automated agent comment, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedI've verified the store lifecycle, the runner wiring, the settings merge, the panel precedence logic, and the shared ticker against the actual source. Here's my assessment.
Summary
This adds an optional dedicated
securityReviewAiconfig for security audits (falling back toreviewAi) plus live/total run-duration display across the panel, dock, and history. The logic is sound and the state modeling is careful — start/end stamps are cleared on re-run, the queue wait is correctly excluded, the shared ticker mounts/unmounts its interval cleanly, andeffectiveReviewAiis threaded consistently through the runner (lane pick, generation, comment model, persisted history). No blockers. Two should-fixes below.Correctness / UX
should-fix —
PrReviewPanel.tsx,needsKey/keyPreviewvs. the Security-audit button. The key warning is computed only for the visible provider (provider = (reviewOverride ?? globalReviewAi)?.provider), never forsecurityReviewAi. Concrete case: review model = Anthropic (key saved), dedicated security model = OpenAI with no OpenAI key saved, no in-panel override. The panel shows no warning (Anthropic has a key), but clicking Security audit routes tosecurityReviewAiand the run fails intophase === "error"with the generic "The review failed. Check the model and try again." — instead of the specific "No OpenAI API key saved" hint the user would get if OpenAI were the general provider. Same gap applies to the CLI not-found / not-signed-in warnings. Suggested fix: when!reviewOverride && securityReviewAiwould be used, also surface a key/availability warning forsecurityReviewAi.provider(e.g. compute a secondneedsKey/preview for it), so the dedicated security model's readiness is visible before the button is clicked.Docs / conventions
should-fix — marketing site not updated. The diff updates
README.md,src/features/help/content.ts, and adds twochangelog.d/fragments, but notsite/src/pages/index.astro. The repo's "keep docs in sync — every time, unprompted" rule lists the marketingcapabilities/FeatureRowas step 2, and the author clearly did the other three surfaces (so this reads as an omission rather than a deliberate "too minor" call, which the notes don't record). Confirm whether the site's AI-review copy needs the dedicated-security-model / runtime mention, or note it as a deliberate skip since these refine an existing capability.Notes acknowledged (not re-flagged)
securityReviewAiabsence semantics (not inDEFAULT_SETTINGS, nested-merge only when present,undefinedon toggle-off) are an explicit recorded decision (note [#2]) and theloadSettingsmerge in the diff implements exactly that; a straynullalso degrades safely viaeffectiveReviewAi's??and the panel's truthiness check."security"(note [#3]) matcheseffectiveReviewAi.ReviewHistory(old records stampedstartedAtat enqueue) are called out inline in the diff and guarded by thefinishedAt - startedAt > 0check, which also naturally hides the old automation records that stampedstartedAt === finishedAt.Posted by GitDesktop — AI output, verify before acting on it.
Related
Tickets:
#2Tickets:
#3Originally posted by: theBGuy
🤖 GitDesktop AI security audit ·
opus· automatedThe changes are self-contained: display of elapsed timers and a new optional
securityReviewAiconfig that flows through the exact samegenerateReviewText/ provider plumbing as the existingreviewAi. Adding a parallel config object that reuses identical code paths introduces no new trust boundary — any host-allowlist / guarded-fetch enforcement that applied toreviewAiapplies identically tosecurityReviewAi.No security issues found in these changes.
securityReviewAi) is the user's own local settings, not attacker-controlled; the Ollama/OpenAI-compatible URLs are user-configurable connections (a non-issue).PrReviewPanel,ReviewHistory, andActivityDockare React-escaped and come from trusted config.formatDuration, the shared ticker inelapsed-time.tsx, and the timestamp plumbing inreviews.ts/runner.tsare pure display/timing logic with no injection sink.Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
Round 1 dispositions
AI review should-fix 1 (security-model readiness warnings) — accepted, with the overturn acknowledged. This finding fairly overturns notes item 5's recorded carve-out ("key/CLI warnings key off the picker's provider only"): the concrete case — review model keyed, security model on a keyless provider, no override — strands the user in a generic run error when the panel already owns the exact warning machinery. Fix in the next push: when the Security-audit button would use the dedicated config (
!reviewOverride && securityReviewAi, same condition as the hint) and its provider differs from the picker's, the panel surfaces the same three readiness warnings (missing API key / CLI not found / CLI not signed in) computed forsecurityReviewAi.provider, phrased as security-audit warnings. Hooks stay unconditional (second secret-preview + detect query, render-gated), and when both configs share a provider the existing warnings already cover it, so nothing doubles up.AI review should-fix 2 (marketing site) — declined; the deliberate-skip record exists. Notes item 15 (posted before the reviews fired) records precisely the call this finding asks to see: site deliberately skipped, "recorded here so the skip is a decision, not an omission." Both features refine the existing AI-review capability line rather than adding a capability — the repo's own bar for a site entry. (The review's notes-acknowledged section suggests the lift may have truncated before item 15; it's in the PR conversation in full.) Same disposition on Copilot's README:411 thread.
Copilot content.ts:1626 (
{{ai}}gating) — accepted and fixed (one-line: the{{ai}}opener moved before the elapsed-timer sentence; every review run is an AI feature, so the timer sentence is AI-only content). In the next push.Bonus evidence for notes item 11: this PR's own round-1 automated reviews are the live confirmation the runner duration fix needed — the persisted history records carry real spans now (general
255.1s, security52.0s, sequential), where every pre-batch automation record hadstartedAt === finishedAt. Item 11's "not yet exercised" caveat is closed.Posted by GitDesktop — automated agent comment, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedVerified against source. My assessment is complete.
This adds an optional dedicated
securityReviewAiconfig (falling back toreviewAiviaeffectiveReviewAi) plus live/total run-duration display across the panel, dock, and history. The logic is sound — the fallback is centralized and threaded consistently through the runner, the start/end stamps clear on re-run and exclude queue wait, and the shared ticker mounts/unmounts cleanly. No blockers. One narrow should-fix and one nit.Resolved since last review
PrReviewPanel.tsxcomputessecurityWarnAppliesand renders the three security-specific readiness warnings (missing key / CLI not found / CLI not signed in) keyed offsecurityReviewAi.provider, so a keyless dedicated security provider no longer strands the user in the generic error state.help/content.ts(Copilot + prev context) — resolved. The{{ai}}opener now precedes "A review still running…", so the timer copy is hidden under Hide AI.Correctness / UX
should-fix —
PrReviewPanel.tsx,securityWarnApplies(line 202) vs. the generalcliDetect(line 176). The security readiness warnings gate onsecurityReviewAi?.provider !== provider, so they are entirely skipped when the security config shares the general provider. But the generalcliDetectrunsdetectAgentCli(cliKind!, reviewAi?.cliPath)against the picker'scliPath, not the security config's. Concrete case: general review =claudewith a workingcliPath, dedicated security =claudewith a customcliPaththat points at a missing/renamed binary, no in-panel override.securityWarnAppliesisfalse(providers match),secCliDetectis disabled, and the general detect (working path) shows no warning — so clicking Security audit dies into the generic "The review failed" state, exactly what these warnings were added to prevent. Suggested fix: also run/surface the CLI readiness check when the providers match butsecurityReviewAi.cliPath !== reviewAi.cliPath(rendering only the CLI-not-found / not-signed-in lines in that sub-case, since a shared provider means a shared key so the key warning stays redundant).Readability
nit —
PrReviewPanel.tsx,secKeyPreview(line 208). UnlikesecCliDetect,useSecretPreview(secProvider)has noenabledgate, so when no security config exists it still fetches the secret preview for the default"anthropic". Harmless (presence-only, and deduped when it matches the picker's provider), but the inline comment claims the hooks stay "cheap when the security path doesn't apply" — which holds for the detect query but not this one. Consider aligning the two or trimming the comment's claim.Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
Round 2 dispositions
should-fix (same-provider / different-
cliPathescapes the readiness gate) — accepted and fixed in the next push. Verified real:securityWarnAppliesrequired provider inequality, so a security config sharing the general provider but pointing at a broken custom CLI path rendered nothing and died generic — an emergent gap in round 1's own fix, fair catch. The gate is now split:providerDiffers(all three warnings, key included) vscliPathDiffers(same provider, different path → the two CLI warnings only — same provider means same key, per the finding's own sub-case reasoning; the two gates are mutually exclusive by construction).secCliDetect'senabledridesproviderDiffers || cliPathDiffers, and the detect query was already keyed on the security config's owncliPath, so the probe tests the right binary.nit (
secKeyPreviewungated vs the "cheap" comment claim) — accepted, aligned rather than trimmed. NowuseSecretPreview(providerDiffers ? secProvider : provider): when the security path is inactive or shares the provider, the argument equals the picker's own call and dedupes to the same cache entry — zero extra fetch. The dedupe claim was verified against the hook's source before relying on it:settingsKeys.secret(provider)is purely provider-keyed (["secret-present", provider]), so identical arguments provably share one entry..datais only read underproviderDiffers, where the argument is the security provider.For the record — why no security-audit round 2: this setup's automations config enables the security audit on
pr-openonly;pr-syncruns the general review alone. The round-1 audit (clean) covered the full diff at ready-flip; push rounds are general-only by configuration, not by malfunction — the per-mode watermark machinery was verified live (realheadShaon both modes' round-1 records).Posted by GitDesktop — automated agent comment, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedThis adds an optional dedicated
securityReviewAiconfig (with a centralizedeffectiveReviewAifallback toreviewAi) plus live/total run-duration display across the panel, dock, and history. The logic is sound and consistent across the manual (PrReviewPanel.run) and automated (runner.run→effectiveReviewAi) paths, the start/end stamps clear on re-run and exclude queue wait, the shared ticker mounts/unmounts cleanly, and the round-2 readiness gaps are now closed. No blockers, and nothing I'd hold the merge for.Resolved since last review
cliPathescapes the readiness gate (prev round-2 should-fix) — resolved in the current diff.PrReviewPanel.tsxnow splits the gate intoproviderDiffers(all three warnings) andcliPathDiffers(CLI warnings only, since a shared provider shares its key).secCliDetect.enabledridesproviderDiffers || cliPathDiffers, and the query is keyed onsecurityReviewAi?.cliPath, so the probe tests the security config's own binary. The concrete stranding case (generalclaudeon a working path, securityclaudeon a broken custom path, no override) now surfaces the CLI-not-found / not-signed-in lines.secKeyPreviewungated vs. the "cheap" comment (prev nit) — resolved. It's nowuseSecretPreview(providerDiffers ? secProvider : provider); when the security path is inactive or shares the provider, the argument equals the picker's own call and dedupes to the same provider-keyed cache entry (.datais only read underproviderDiffers). VerifiedsecProvideronly flows into the fetch underproviderDiffers.Triage of other tools' findings
help/content.ts:1626(elapsed-timer sentence outside the AI gate) — resolved in the current diff: the{{ai}}opener now precedes "A review still running… shows a live elapsed timer", so the copy is hidden under Hide AI.README.md:411(marketing site not updated) — the author recorded this as a deliberate skip (notes item 15: both features refine the existing AI-review capability line, below the site-entry bar). Acknowledged, not re-flagged.The rest checks out:
effectiveReviewAiand the panel'srun()resolve the same effective config (override → security config formode === "security"→globalReviewAi), the runner threadsreviewCfguniformly through lane pick / generation / comment label / persisted model, the store clearsstartedAt/endedAton re-run and stampsstartedAtonly afteracquireSlot(queue wait excluded), andformatDurationclamps non-finite/negative input to"0s".ElapsedTime's ticker uses stable module-levelsubscribe/getNow, refreshes on the 0→1 transition, and clears on 1→0 — no leak and StrictMode-safe.Posted by GitDesktop — AI output, verify before acting on it.
Ticket changed by: theBGuy