diagnose incident: cloud-side answer to "why is my project 504ing / down" - #228
Conversation
…own' New subcommand hitting GET /projects/v1/:id/diagnose/incident — the report is built entirely from platform-side sources, so the command keeps working while the instance itself is wedged or dead (exactly when 'diagnose logs' stops answering). Prints the verdict, the plain-language explanation, the facts behind it, and what to do next; --json passes the raw report through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kbhm3x6R6B4vy5Z2H8mKb
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
WalkthroughAdds the ChangesIncident diagnostics
Estimated code review effort: 3 (Moderate) | ~25 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
jwfing
left a comment
There was a problem hiding this comment.
Summary
This PR is close, but the non-JSON renderer currently omits some of the backend evidence that the new diagnose incident command is supposed to surface.
Requirements Context
I based intent on the PR description and the existing diagnose documentation in README.md, because I did not find any repo-local spec or README section for diagnose incident itself. The PR description says this command should explain why a project is down/504ing, including the verdict, plain-language explanation, the facts behind it, and next steps, while --json passes through the raw report.
Findings
Critical
src/commands/diagnose/incident.ts:15-33,82-109The backend contract includes incident-specific evidence fields such asproject_status,operation_status, andrecent_platform_operations, but the human-readable output never prints any of them. It only renders reachability, timestamps, memory, scrape gaps, and instance type. That means verdicts likepaused_or_suspendedorplatform_operation_in_progresslose the very facts that explain why the project is down, so the command does not fully meet the PR’s stated requirement to show the “facts behind” the incident.
Suggestion
src/commands/diagnose/incident.ts:49-124There is no automated coverage for the new command path. A focused test around--json, theFAKE_PROJECT_IDrejection, and at least one non-JSON incident payload (especially a platform-operation verdict) would make the output contract much safer to evolve.README.md:1113-1143,src/commands/diagnose/index.ts:379-383The public docs still list onlyadvisor,db,logs, andmetricsunderdiagnose. Addingdiagnose incidentto the README would keep the CLI’s documented surface aligned with the shipped commands.
Information
- (none)
Verdict
request_changes — one correctness issue blocks approval; I did not identify any new security-relevant or performance-relevant issues in this diff.
Greptile SummaryThe PR adds a platform-backed
Confidence Score: 4/5The PR is not yet safe to merge because active project reports still omit project-status evidence from human-readable output. The formatter continues to suppress Files Needing Attention: src/commands/diagnose/incident.ts
|
| Filename | Overview |
|---|---|
| src/commands/diagnose/incident.ts | Implements fetching, normalization, and rendering for incident reports; the previously reported omission of active project status remains. |
| src/commands/diagnose/incident.test.ts | Covers report normalization and major rendering paths but does not establish that active project status is rendered. |
| src/commands/diagnose/index.ts | Registers the new incident subcommand under the existing diagnose command group. |
| README.md | Documents incident diagnosis, authentication requirements, and JSON usage. |
Sequence Diagram
sequenceDiagram
participant User
participant CLI
participant Platform
User->>CLI: diagnose incident
CLI->>CLI: Require login and linked Platform project
CLI->>Platform: GET /projects/v1/:id/diagnose/incident
Platform-->>CLI: Incident report
alt --json
CLI-->>User: Raw server payload
else Human-readable
CLI->>CLI: Normalize and format report
CLI-->>User: Verdict, evidence, and recommendation
end
Reviews (5): Last reviewed commit: "label the metrics_stopped verdict added ..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/commands/diagnose/incident.ts (1)
93-109: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSurface
project_statusandrecent_platform_operationsin the text output.The report includes
project_status,operation_status, andrecent_platform_operations, but the text branch never prints them. For thepaused_or_suspendedandplatform_operation_in_progressverdicts, these fields carry the decisive evidence. Users who do not pass--jsonlose it.♻️ Proposed additions to the facts list
+ facts.push( + `Project status: ${report.project_status}${report.operation_status ? ` (operation: ${report.operation_status})` : ''}`, + ); if (report.down_since) { facts.push(`Down since: ${formatWhen(report.down_since)}`); } @@ if (report.instance_type) { facts.push(`Instance type: ${report.instance_type}`); } + for (const op of report.recent_platform_operations ?? []) { + facts.push(`Recent platform operation: ${op.action} at ${formatWhen(op.at)}`); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/commands/diagnose/incident.ts` around lines 93 - 109, Update the text facts-building branch around the existing report fields to include project_status and recent_platform_operations, and include operation_status where relevant. Ensure paused_or_suspended and platform_operation_in_progress outputs surface this evidence, while preserving the existing formatting and conditional style used by the facts list.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/commands/diagnose/incident.ts`:
- Line 73: After parsing the response in the incident command, validate that the
report contains a non-null object reachable before any non-JSON rendering path
reads report.reachable.metrics_reporting or report.reachable.database_connect.
Route missing or invalid reachable data through handleError with a clear API
error, while preserving normal rendering for valid IncidentReport responses.
---
Nitpick comments:
In `@src/commands/diagnose/incident.ts`:
- Around line 93-109: Update the text facts-building branch around the existing
report fields to include project_status and recent_platform_operations, and
include operation_status where relevant. Ensure paused_or_suspended and
platform_operation_in_progress outputs surface this evidence, while preserving
the existing formatting and conditional style used by the facts list.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5d8fd0f6-3baa-4052-bdc3-b242f480a47d
📒 Files selected for processing (2)
src/commands/diagnose/incident.tssrc/commands/diagnose/index.ts
jwfing
left a comment
There was a problem hiding this comment.
Review: diagnose incident
Summary: A clean, tightly-scoped addition of a cloud-side diagnose incident subcommand that faithfully mirrors the existing diagnose metrics pattern; no blocking issues found.
Requirements context
No matching spec/plan found — docs/superpowers/ is empty and the design docs under docs/specs/ (2026-03-27-diagnose-command-design.md, 2026-03-27-diagnose-implementation-plan.md) predate and do not mention an incident command. Assessed against the PR description and the intent stated there (platform-side "why is my project down/504ing" answer that works while the instance is unreachable), plus the backend counterpart InsForge/insforge-cloud-backend#814.
Findings
Critical
(none)
Suggestion
-
Software engineering — no test coverage for the new command (
src/commands/diagnose/incident.ts:1-125). There are no tests for the command. This is consistent with the existing convention — none of the siblingdiagnosecommands (metrics/logs/advisor/db) have command-level tests; the whole test suite lives undersrc/lib/. So this is not a blocker. That said, the command has non-trivial pure logic worth pinning: the fact-list assembly (incident.ts:76-104), the verdict-label fallbackVERDICT_LABELS[report.verdict] ?? report.verdict(incident.ts:85), and thememory_before_down_pctvsmemory_latest_pctbranch (incident.ts:88-92). Extracting these into a small pureformatReport(report)helper and adding a focused vitest would guard the human-readable output against regressions without needing to mock the network. -
Functionality — backend fields returned but never surfaced in text output (
src/commands/diagnose/incident.ts:16-33vs73-104).project_status,operation_status, andrecent_platform_operationsare declared onIncidentReportand returned by the platform, but only ever emitted via--json. In particular, when the verdict isplatform_operation_in_progress, the human-readable output shows no operation facts at all — the user relies entirely on the backend-suppliedexplanation. Consider rendering the most recent entry fromrecent_platform_operationsas a fact (e.g.Recent platform op: <action> at <when>) so the non-JSON path is self-explanatory. Low blast radius sinceexplanationlikely covers it.
Information
- Robustness of
formatWhen(src/commands/diagnose/incident.ts:36-39). Only thenullcase is guarded; a malformed/non-ISO timestamp would renderInvalid Daterather thanunknown. The backend controls the format, so risk is low. !== nulltreats an absent field as present (incident.ts:88).memory_before_down_pct !== nullrendersundefined%if the backend ever omits the field instead of sending explicitnull. The interface types it as requirednumber | null, so this is contract-safe today — noted only in case the backend contract drifts. (scrape_gaps_24h > 0atincident.ts:97is safe againstundefinedby contrast.)- Unguarded
report.reachabledereference (incident.ts:76-83). A malformed response missingreachablewould throw aTypeErrorrather than a cleanCLIError. This matches how the siblings dereference response bodies (e.g.metrics.tsusesdata.metricsdirectly), so it's consistent with the codebase — just flagging the shared assumption. - Graceful 404 handling confirmed. The PR notes the endpoint 404s until the backend deploys;
platformFetch(src/lib/api/platform.ts:130-135) throws aCLIErroron any non-2xx, whichhandleErrorrenders cleanly — so an early merge degrades to a readable error rather than a crash. Good.
Dimension notes
- Security — no concerns. Read-only
GET; the path segment is the caller's own linkedproject_idfrom local config, not free-form user input;requireAuth+ theFAKE_PROJECT_IDguard correctly reject--api-keylink mode (matchingdiagnose metrics); no secrets logged (the debug path inplatformFetchredacts the bearer); no new dependencies. - Performance — no concerns. A single platform
GET, no loops over the network, no N+1, trivial O(n) fact assembly. - Conventions — clean. Imports,
requireAuth(apiUrl), theFAKE_PROJECT_IDguard,trackDiagnose('incident', config),reportCliUsage('cli.diagnose.incident', …), and thetry/catch/finally+handleErrorshape all matchmetrics.tsexactly.registerDiagnoseIncidentCommandis wired intoregisterDiagnoseCommands(index.ts:383). Correctly kept out of the aggregatediagnosereport, since that path assumes instance reachability while incident is the "box is dead" fallback.
Verdict
approved (informational — no Critical findings; the human still gives the explicit GitHub approval via the approve flow). The two Suggestions are non-blocking; the strongest is adding a small unit test around the report-formatting logic.
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
r2d2 caught that the human-readable output dropped the evidence fields for the boring verdicts (project status, in-flight operation, recent platform operations) — the rendering is now a pure exported formatIncidentReport covering every backend field, with 4 unit tests locking the output contract. Malformed platform responses get a clear CLIError instead of a raw TypeError (cubic), and the README documents the new subcommand. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kbhm3x6R6B4vy5Z2H8mKb
jwfing
left a comment
There was a problem hiding this comment.
Review — diagnose incident
Summary: A clean, convention-following addition of a cloud-side diagnose incident subcommand that faithfully mirrors the existing diagnose metrics command; the pure formatter is well tested and there are no blocking issues, only minor robustness/test hardening opportunities.
Requirements context
No spec/plan under docs/specs/ matches this subcommand — docs/specs/2026-03-27-diagnose-command-design.md is the diagnose design of record but predates incident (it enumerates only metrics, advisor, db, logs, and the comprehensive report). There is no docs/superpowers/. I assessed against the PR description and the backend counterpart InsForge/insforge-cloud-backend#814. The implementation is consistent with the design's stated conventions (dual --json, platformFetch bearer auth, requireAuth + project-config precondition, reportCliUsage('cli.diagnose.*', ...)).
Findings
Critical
(none)
Suggestion
- Functionality / robustness —
src/commands/diagnose/incident.ts:127-131. The malformed-response guardtypeof report.reachable !== 'object'does not catchreport.reachable === null, becausetypeof null === 'object'. A payload withreachable: nullslips past the guard and then throws an unhelpfulTypeError: Cannot read properties of null (reading 'metrics_reporting')insideformatIncidentReport(incident.ts:53), instead of the intendedCLIErrorwith the friendly "your backend may predate this command" message. Recommend tightening to!report.reachable || typeof report.reachable !== 'object'. - Software engineering / test coverage —
src/commands/diagnose/incident.test.ts. The tests thoroughly cover the pureformatIncidentReportoutput contract (all verdicts, forward-compat unknown verdict, project status / in-flight op / recent ops) — nice. But the.action()path is untested: theFAKE_PROJECT_IDrejection, theProjectNotLinkedErrorprecondition, and the malformed-response guard. A direct test of the guard would have surfaced thereachable: nullhole above. Non-blocking, but a guard-path test is cheap insurance for the defensive code that exists precisely for backend/version drift.
Information
- Functionality — guard message vs. real 404 path (
incident.ts:129). The guard's message ("Your backend may predate this command") only fires on a200response with the wrong shape. In the actual "backend not yet deployed" scenario the route404s, andplatformFetch(src/lib/api/platform.ts:131-135) throwsRequest failed: 404first — so the tailored hint rarely reaches the user who most needs it. Consider mapping a 404 on this endpoint to the same "predates this command" guidance. - Functionality — timezone rendering (
incident.ts:46-49).formatWhenusesnew Date(iso).toLocaleString(), which renders in the CLI host's local timezone with no TZ label. For an incident/SRE tool where operators may be comparing against UTC dashboards, an explicit timezone (or ISO/UTC) would reduce ambiguity. Tests are locale-agnostic, so this is purely a UX nicety. - Cross-PR awareness — verdict evidence depends on backend #814. The CLI faithfully renders whatever
recent_platform_operationsthe backend supplies. Verdict accuracy (e.g. distinguishing a genuine OOM from a platform-drivenupgrade_instance/restorerestart) therefore hinges on the backend sourcing those operations correctly. Worth confirming in #814 that resize/restore operations surface inrecent_platform_operations, so an operation-induced restart isn't rendered as an unexplainedoom_likely. Backend scope — flagged only for awareness; nothing to change here.
Notes (positive)
- Correctly reuses the shared
shutdownAnalytics()(which nulls the client first, so the catch+finally double-call is intentional and safe) — matchesmetrics.tsexactly. - No security concerns:
project_idis a config-sourced UUID interpolated into the path (not free user input), no new dependencies, no secrets logged (bearer redaction lives inplatformFetch). - No performance concerns: a single fetch and O(n) line rendering; no loops/allocations of note.
- README entry and
diagnose/index.tsregistration are correct and in the right place.
Verdict
approved (informational — no Critical findings; the two Suggestions and the Information notes are non-blocking). The reachable: null guard hole is the only one I'd encourage fixing before merge, but it's low-blast-radius against your own backend. Human GitHub approval remains a separate manual step.
jwfing
left a comment
There was a problem hiding this comment.
Summary
This PR adds a useful cloud-side diagnose incident command, but the current payload validation is too shallow to support the malformed-response/backward-compatibility behavior the PR claims.
Requirements context
I based intent on the PR description first, plus the new README entry in README.md, because I did not find any separate issue/spec for this command in the repo. The PR description explicitly says this command should explain outages from the platform side, support --json passthrough, reject --api-key link mode, and include a response-shape guard with a clear CLIError on malformed payloads. I also checked DEVELOPMENT.md for command and telemetry conventions.
Findings
Critical
- src/commands/diagnose/incident.ts:129 The new "shape guard" only verifies that
reportis an object andreport.reachableis an object. After that,formatIncidentReport()blindly iteratesreport.recent_platform_operationsand reads other fields as if they are correctly typed. A partially deployed or malformed backend payload such as{ reachable: {}, recent_platform_operations: null }will still throw a rawTypeError, and missing string fields will render asundefined, which contradicts the PR's stated requirement to surface malformed payloads as a clearCLIError. Because this command is specifically meant to keep helping when the backend/instance state is unusual, that validation needs to cover every field the renderer consumes, not justreachable.
Suggestion
- src/commands/diagnose/incident.ts:143, DEVELOPMENT.md:58 The new command still adds
reportCliUsage(...), but the repo guide says not to extend that legacy telemetry path for new commands and to use PostHog only going forward. - README.md:1146, src/commands/diagnose/incident.ts:117 The README documents the new command but omits the platform-login requirement /
--api-keyrejection that the PR description calls out and the implementation enforces. Users reading the CLI docs alone will only discover that limitation at runtime.
Information
- src/commands/diagnose/incident.test.ts:30 The added tests cover formatter happy paths and unknown verdict fallback, but they do not cover malformed backend payloads or the command action path, which is why the validation bug above is currently untested.
- package.json:10 I could not execute the new Vitest file in this checkout because
npm test -- --run src/commands/diagnose/incident.test.tsfailed withsh: vitest: not found, so this review is based on static inspection. - No new security-relevant issues found in the changed code.
- No notable performance concerns found; this is a single platform request plus lightweight formatting.
Verdict
request_changes because the malformed-response handling does not meet the PR's stated behavior and can still fail with an unstructured runtime error.
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/commands/diagnose/incident.ts">
<violation number="1" location="src/commands/diagnose/incident.ts:61">
P3: The `project_status !== 'active'` check silently omits the project status line from the report whenever the project is active, even though the function's own doc comment promises every evidence field the backend sends will be shown alongside the verdict. This means healthy/active-project incident reports never display the project status fact.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| lines.push(report.explanation); | ||
| lines.push(''); | ||
| const facts: string[] = []; | ||
| if (report.project_status !== 'active') { |
There was a problem hiding this comment.
P3: The project_status !== 'active' check silently omits the project status line from the report whenever the project is active, even though the function's own doc comment promises every evidence field the backend sends will be shown alongside the verdict. This means healthy/active-project incident reports never display the project status fact.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/diagnose/incident.ts, line 61:
<comment>The `project_status !== 'active'` check silently omits the project status line from the report whenever the project is active, even though the function's own doc comment promises every evidence field the backend sends will be shown alongside the verdict. This means healthy/active-project incident reports never display the project status fact.</comment>
<file context>
@@ -46,6 +46,62 @@ function formatWhen(iso: string | null): string {
+ lines.push(report.explanation);
+ lines.push('');
+ const facts: string[] = [];
+ if (report.project_status !== 'active') {
+ facts.push(`Project status: ${report.project_status}`);
+ }
</file context>
r2d2's exact counter-example ({reachable: {}, recent_platform_operations:
null}) now normalizes cleanly: normalizeIncidentReport coerces every
field the renderer consumes with defensive defaults and throws a clear
CLIError only when the payload is not a report at all — this command
exists precisely for moments when backend state is unusual. Drops the
legacy reportCliUsage path per DEVELOPMENT.md (PostHog trackDiagnose
stays). README notes the platform-login requirement. 7 tests including
the malformed-payload cases.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kbhm3x6R6B4vy5Z2H8mKb
jwfing
left a comment
There was a problem hiding this comment.
Summary
This PR adds a useful diagnose incident command, but the current --json behavior does not match the stated contract and should be fixed before merge.
Requirements Context
I based this review on the PR description and the new README entry in README.md, because I did not find a separate issue/spec in the repo documenting this command. The stated intent is: add insforge diagnose incident, have it work from the platform side when the instance is down, render a human-readable report, and have --json pass the raw backend report through unchanged.
Findings
Critical
- src/commands/diagnose/incident.ts:
--jsonis advertised in the PR description as a raw passthrough, but the command always runs the response throughnormalizeIncidentReport()before callingoutputJson()(src/commands/diagnose/incident.ts). That normalization rewrites omitted fields to defaults, drops malformedrecent_platform_operationsentries, and discards any backend fields not listed inIncidentReport. For machine consumers, this is a contract break:--jsonno longer returns the exact server payload, and future backend additions will be silently lost.
Suggestion
- src/commands/diagnose/incident.test.ts: test coverage only exercises the pure formatter/normalizer helpers, not the command action itself (src/commands/diagnose/incident.ts). That leaves the endpoint path, auth/link-mode gating, and especially the
--jsonpassthrough contract untested. A command-level test would have caught the blocker above.
Information
- src/commands/diagnose/incident.ts: no security-relevant issues stood out. The command uses the existing
requireAuth/platformFetchpath and does not introduce new secret handling or unsafe input flows. - src/commands/diagnose/incident.ts: no material performance concerns stood out. The implementation is a single platform request plus bounded local formatting.
Verdict
request_changes because the --json output does not satisfy the PR’s documented contract.
jwfing
left a comment
There was a problem hiding this comment.
Review: diagnose incident — cloud-side "why is my project down/504ing?"
Summary: A well-scoped, defensively-written addition that closely mirrors the existing diagnose metrics command; no blocking issues found.
Requirements context
No matching spec/plan under docs/specs/ — the two diagnose docs (2026-03-27-diagnose-command-design.md, 2026-03-27-diagnose-implementation-plan.md) predate this subcommand and contain no mention of incident/verdict/504/OOM. Assessed against the PR description and the sibling command (metrics.ts) as the source of intent. Backend counterpart is InsForge/insforge-cloud-backend#814 (GET /projects/v1/:id/diagnose/incident), which is not verifiable from this repo.
Findings
Critical
(none)
Suggestion
- Functionality / cross-repo contract —
src/commands/diagnose/incident.ts:36-42.VERDICT_LABELShard-codes the keypaused_or_suspended, but the tested/PR-documented verdict family usesplatform_operation_in_progress,oom_likely,down_unknown,no_incident_detected. Please confirmpaused_or_suspendedmatches the exact string backend #814 emits for the paused case. This isn't a crash risk — an unrecognized verdict degrades gracefully to the raw string (there's even a forward-compat test for that atincident.test.ts:56-59) — but a silent label mismatch would ship a rougher message than intended. Worth a one-line check against the backend enum before merge.
Information
- Functionality — pre-deploy 404 UX,
incident.ts:195-199+src/lib/api/platform.ts:131-135.platformFetchthrows a genericCLIError(err.error ?? "Request failed: 404") on any non-2xx before the response reachesnormalizeIncidentReport, since nopassThroughStatusesis passed. The friendly "Unexpected response from the platform … Your backend may predate this command" message therefore only fires on a200-with-wrong-body — not on the realistic "backend not deployed yet → 404" case the PR notes calls out. If you want that guidance to actually surface pre-deploy, passpassThroughStatuses: [404]and map 404 to the same CLIError. Non-blocking; the command still errors cleanly today. - Software engineering —
incident.ts:126-129(formatWhen). Guardsnull/empty but not an unparseable date string, so a malformed ISO value from the backend would render asInvalid Date. Cosmetic only;normalizeIncidentReportalready keeps these as strings by design. - Consistency (validated, not a gap) — analytics. Unlike
metrics.ts/logs.ts, this command intentionally omitsreportCliUsage('cli.diagnose.…'). That is the right call here:reportCliUsagePOSTs to the instance's ownoss_host(src/lib/skills.ts:202), and this command runs precisely when the instance is unreachable — the report would just burn the 3s abort timeout against a dead box.trackDiagnose(platform-side PostHog) is correctly retained. Flagging only so it reads as deliberate.
What's good
normalizeIncidentReportis genuinely defensive: every rendered field has a fallback, malformed operation entries are filtered, non-finite numbers coerce tonull/0, and it throws a clearCLIErroronly when the payload isn't a report — appropriate for a command that runs during abnormal backend/instance states.- First
diagnosesubcommand to ship a unit test file (incident.test.ts, 7 tests) covering both the output contract and the normalization edge cases — coverage is stronger than its siblings. - Correctly reuses the platform-login gating convention (rejects unlinked +
FAKE_PROJECT_ID/--api-key), andshutdownAnalyticsis idempotent so the catch+finally double-call is safe.
Security: No security-relevant changes — project_id comes from the user's own local config and is interpolated into a path to their own project; no new user input reaches SQL/shell, the bearer is redacted in debug output, and no secrets/PII are logged or added to output.
Performance: Single platform request, no loops or hot-path work; nothing of concern.
Verdict
approved (informational — zero Critical findings; the Suggestion and Information items are non-blocking). Human approval via the normal GitHub flow. Note the PR's own gating: merge only after backend #814 deploys, per the PR description.
Normalization rewrote the machine-facing payload (defaults injected, unknown fields dropped) — a contract break for JSON consumers. --json now emits the exact server payload; the defensive normalization only feeds the human-readable renderer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kbhm3x6R6B4vy5Z2H8mKb
jwfing
left a comment
There was a problem hiding this comment.
Summary
This PR adds insforge diagnose incident in a way that matches the stated feature intent and existing diagnose-command conventions, with no blocking issues found.
Requirements context
I based intent on the PR description plus the new README entry in README.md, because I did not find separate design/spec docs or linked requirements in the checkout. The intended behavior is: add a new cloud-side diagnose incident subcommand, require platform login rather than --api-key link mode, print either a human-readable incident report or raw JSON, and keep working when the instance itself is unreachable. I could not execute the test suite in this workspace because the dev dependency runner is not installed here (vitest: not found).
Findings
Critical
(none)
Suggestion
src/commands/diagnose/incident.ts:172-214,src/commands/diagnose/incident.test.ts:1-109— The tests only exercise the pure formatter/normalizer helpers. They do not cover command-level behavior such as rejectingFAKE_PROJECT_IDlink mode, preserving raw server payload under--json, or confirming the subcommand is actually wired intodiagnose. Those are the highest-risk regressions for this feature, so an integration-style command test would materially improve confidence.src/commands/diagnose/incident.ts:111-113—formatWhen()treats any non-empty string as a date and will printInvalid Dateif the backend returns malformed timestamps. Given this command is explicitly meant for degraded/unusual platform states and already normalizes the rest of the payload defensively, it would be safer to fall back to the original string orunknownwhennew Date(iso)is invalid.
Information
src/commands/diagnose/incident.ts:181-201— No security-relevant issues found. The command performs a single authenticated platform fetch, rejects API-key link mode as documented, and does not add any new secret handling or shell/SQL surfaces.src/commands/diagnose/incident.ts:121-169,src/commands/diagnose/incident.ts:191-196— No performance-relevant issues found. The implementation is a single request plus linear-time normalization/formatting over a small response payload.
Verdict
approved
jwfing
left a comment
There was a problem hiding this comment.
Review: diagnose incident subcommand
Summary: A clean, well-tested cloud-side diagnose incident command whose IncidentReport interface is an exact mirror of the paired backend endpoint — no blocking issues; one worth-fixing label gap and a couple of cosmetic notes.
Requirements context
No incident-specific spec exists under docs/specs/ (contents: 2026-03-27-diagnose-command-design.md, 2026-03-27-diagnose-implementation-plan.md, 2026-04-17-db-migrations-command-design.md) — none matches this subcommand. Assessed against the PR description and the paired backend PR InsForge/insforge-cloud-backend#814 (GET /projects/v1/:id/diagnose/incident), which I read to verify the request/response contract.
I verified the CLI IncidentReport interface (src/commands/diagnose/incident.ts:12-32) field-for-field against the backend's response shape (src/services/incident.service.ts IncidentReport + res.json(report) in incident.controller.ts): project_status, operation_status, instance_type, reachable.{metrics_last_seen_at,metrics_reporting,database_connect}, down_since, memory_before_down_pct, memory_latest_pct, postgres_started_at, scrape_gaps_24h, recent_platform_operations[].{action,at}, verdict, explanation, recommendation — all match.
Critical
(none)
Suggestion
Functionality — one backend verdict is missing a human label. The backend defines six verdicts (incident.service.ts:102-108): paused_or_suspended, platform_operation_in_progress, oom_likely, down_unknown, metrics_stopped, no_incident_detected. The CLI's VERDICT_LABELS (src/commands/diagnose/incident.ts:36-42) maps only five — metrics_stopped is absent. That verdict is emitted on a real, common path (DB reachable but metrics stale — incident.service.ts:418-424), not a hypothetical future one. formatIncidentReport falls back to the raw string (VERDICT_LABELS[report.verdict] ?? report.verdict, incident.ts:118), so it degrades gracefully to Verdict: metrics_stopped rather than a polished label like "Metrics stopped reporting". Non-blocking (the explanation/facts/recommendation all still render), but since it ships alongside #814 it reads as an oversight rather than forward-compat. Adding the sixth label — and a test asserting every backend verdict maps — would close it. The existing falls back to the raw verdict string test (incident.test.ts:56-59) is aimed at genuinely-unknown future verdicts, so it doesn't catch this.
Information
- Empty recommendation prints a bare line. When the backend sends
recommendation: ''(e.g. theno_incident_detectedpath,incident.service.ts:210),formatIncidentReportunconditionally pushesWhat to do: ${report.recommendation}(incident.ts:159), yielding a danglingWhat to do:with nothing after it. Consider skipping the line when the recommendation is empty. formatWhenoutput is locale/timezone-dependent.new Date(iso).toLocaleString()(incident.ts:132-135) renders in the host locale/TZ, and a malformed-but-non-empty date string surfaces asInvalid Date(theasNullableStringguard only filtersnull/empty). Acceptable for a human-facing CLI, and the tests correctly avoid asserting the formatted date — just noting it.
Notes on things I checked and found correct
reportCliUsageomission is deliberate and correct. Unlike siblingmetrics/logs, this command intentionally does not callreportCliUsage— that helper reports to the instance (oss_host), which is exactly what's down during an incident. Keeping onlytrackDiagnose(platform-side PostHog) is the right call and matches the PR's stated "removed legacy telemetry".- Error/analytics lifecycle matches convention. The
try/catch(shutdownAnalytics → handleError)/finally(shutdownAnalytics)shape mirrorsmetrics.ts/logs.ts;shutdownAnalyticsnulls its client so the double-call is idempotent, andhandleErrorprocess.exits as elsewhere. - Defensive normalization is thorough.
normalizeIncidentReportcoerces every rendered field, drops malformed operation entries, rejects non-report payloads with a clearCLIError, and the--jsonpath passes the raw server payload through (forward-compatible with unknown fields). Good. - Security: no security-relevant changes — no user-supplied input reaches SQL/shell/HTTP (the
project_idin the path comes from local link config, same as siblings), no secrets logged,requireAuth+ platform-login guard (FAKE_PROJECT_IDrejected) preserved, no new dependencies. - Performance: no concerns — a single
platformFetchwith no loops, small payload, no blocking work. - Tests/build: the 7 unit tests pass locally;
tsc --noEmitproduces no new errors inincident.ts/incident.test.ts(the pre-existingprompts.tsand other unrelated errors reproduce onmain).
Verdict
approved — no Critical findings. The metrics_stopped label is a genuine (non-blocking) gap I'd encourage fixing before/with merge; the rest are optional polish. As the PR notes, land this only after #814 deploys, or the endpoint 404s. (Informational verdict — human approval via the normal flow.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kbhm3x6R6B4vy5Z2H8mKb
What
insforge diagnose incident— asks the platform (not the instance) why the project is down or returning gateway timeouts, so it works even while the box is wedged or dead — exactly whendiagnose logsstops answering.Backend counterpart: InsForge/insforge-cloud-backend#814 (GET
/projects/v1/:id/diagnose/incident). Output = verdict (paused / platform operation / OOM likely / down unknown / no incident), plain-language explanation, the facts behind it, and what to do next.--jsonpasses the raw report through.Notes
--api-keylink mode rejected, same asdiagnose metrics)npm run buildgreen;npx tsc --noEmithas one pre-existing error insrc/lib/prompts.tsthat reproduces identically on pristine main (clack types drift), untouched by this PR🤖 Generated with Claude Code
https://claude.ai/code/session_016kbhm3x6R6B4vy5Z2H8mKb
Summary by cubic
Adds a cloud-side
diagnose incidentcommand to explain why a project is down or 504ing, even when the instance is unreachable. Human-readable output shows full evidence;--jsonreturns the exact server payload.New Features
diagnose incidentsubcommand calling GET/projects/v1/:id/diagnose/incident, registered underdiagnosewith analytics.metrics_stopped;--jsonis a raw passthrough.CLIErroronly when the payload isn’t a report; 7 unit tests; README entry; removed legacy telemetry (kepttrackDiagnose).Migration
--api-keylink mode.Written for commit be0726a. Summary will update on new commits.
Note
Add
diagnose incidentsubcommand to explain project 504s and downtimeinsforge diagnose incidentsubcommand insrc/commands/diagnose/incident.tsthat fetches/projects/v1/{project_id}/diagnose/incidentfrom the platform API.--json) or a human-readable report covering verdict, metrics reporting status, DB connectivity, memory usage, Postgres start time, scrape gaps, and a recommendation.CLIErrorin both cases.cli.diagnose.incidenton success and failure.Changes since #228 opened
registerDiagnoseIncidentCommandto output raw server JSON payload when invoked with--jsonflag, while maintaining normalized and formatted output for human-readable mode [8bbef47]VERDICT_LABELSconstant [be0726a]Macroscope summarized 007bbb9.
Summary by CodeRabbit
diagnose incidentcommand for investigating service incidents.