Skip to content

Show startup progress before the agent's first message - #1692

Merged
nadaverell merged 3 commits into
mainfrom
feat/investigation-startup-signals
Sep 9, 2026
Merged

nadaverell merged 3 commits into
mainfrom
feat/investigation-startup-signals

Conversation

@nadaverell

@nadaverell nadaverell commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

After an investigation starts, the Activity pane showed only "Starting investigationâ€Ķ" and a timer for the 20+ seconds until the agent's first message. Three real signals exist before that message and none was shown. This PR reports the two Radar can observe honestly, with no timer-based labels:

  • Connected to Radar's tools the moment the agent completes the MCP handshake on the private investigation mount (agent-agnostic).
  • Claude Code ready · <model> · <n> Radar tools from Claude Code's stream-json system/init line. A non-connected MCP server becomes a visible warning in the Activity pane instead of a silent tool-less investigation. Cursor's init names the model only.

Also on the same Activity rows: sub-2s tool durations are hidden on the collapsed row (whole seconds above that), the exact milliseconds stay in the expanded result header, and a failed tool row shows the producer's own failure reason inline.

What changed

  • internal/investigationrefs: a scope now carries a Connected() channel; Registry.MarkConnected(scope) closes it once for a live scope. This is the seam between the MCP mount and the runner, so internal/mcp still does not import internal/ai.
  • internal/mcp/server.go: the investigation middleware marks the scope connected after initialize or tools/list returns without error — a rejected handshake never reports as connected.
  • internal/ai/diagnoser.go: StreamEvent gains optional Model, ToolCount, MCPServers (phase events were already persisted in the run log; no migration). The runner watches the scope and emits one phase: connected per turn, serialized with the parser's events and always before the turn returns — including when the handshake and the stream's end coincide. The watcher stays alive until process exit. parseStream handles system/init and emits phase: ready with the model, the mcp__radar__ tool count and every MCP server status. Cursor emits the model.
  • web/src/api/diagnose.ts, parts.tsx, InvestigationView.tsx: the phase event folds into turn.startup (furthest phase wins, since the handshake and init are reported by different goroutines). The pending status line shows the startup label only while the turn is running with no items, so it never renders on a replayed finished turn and never becomes a transcript item. The MCP warning is attributed to startup and survives replay, since it is a fact of the run; its copy is conditional on the server's state and reads in past tense on a replayed run.
  • Tool rows: toolDurationLabel (hidden under 2s, whole seconds above), toolErrorReason (JSON error field or first non-empty line of the producer text, with Radar's retry hints after the em dash dropped), middleTruncate so a long reason keeps its identifier tail, arguments shrink before the reason on the collapsed row, PayloadBlock header detail for the exact ms.
  • Running status: the elapsed counter is a row-level "Ns elapsed" figure set apart from the label, so "Connected to Radar's tools" no longer reads as if the handshake took that long.

Testing

  • make tsc, make build, go test ./... (both modules), git diff --check — pass. New Go tests: the Claude init parser, the handshake-emitted-once runner path including the short-turn race, the registry handshake, the middleware's success-only marking, and Cursor's init model.
  • packages/k8s-ui: 3549 passed. web: 1023 passed across 87 files, including the startup-signal merge (furthest phase wins, init facts kept), the bare ready label with no init facts, replay suppression, and the tool-row duration/reason rendering.
  • Visual test against a live GKE cluster (Deployment dev/cloud-provisioner, real Claude Code run). Observed from the click: 0.4s "Claude Code startingâ€Ķ", 0.9s "Connected to Radar's tools", 5.0s "Claude Code ready · claude-sonnet-5 · 23 Radar tools", 23.2s first tool row. Reloading a finished run shows no startup label. A follow-up turn on the same run repeated the progression without special-casing.
  • Tool rows on the same cluster: a call for a nonexistent Secret rendered resource not found: secret "â€Ķ" not found inline after the arguments. Every live call finished under 2s, so the slow row was verified against a saved run whose search took 3754ms — the collapsed row reads 4s, the expanded header ORIGINAL RESULT · 3754ms.

Scope and tradeoffs

  • Codex (codex exec --json) only emits thread.started; it carries no model or MCP data, so it gets the agent-agnostic "connected" signal only.
  • The startup warning is hand-rolled rather than AlertBanner. That component is the drawer-scale renderer surface (mb-4 p-3, 14px title plus message); the Activity pane is an 11px transcript and every warning in parts.tsx (consent, apply, truncation notes) is hand-rolled at that scale, so the banner would be out of register there.
  • Radar Hub's hosted transport forwards only the phase name for phase events, so a hosted run shows the bare "<agent> ready" label with no model, tool count or warning. That is the same code path with fields absent — there is no version fallback.
  • The "starting" label uses the agent's display name ("Claude Code startingâ€Ķ") rather than the literal "Agent startingâ€Ķ"; the label is right there and reads better.
  • The 5s to 23s gap between "ready" and the first tool is the model's first thinking; removing it needs --include-partial-messages, which is the planned follow-up.

ðŸĪ– Generated with Claude Code

https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW


Note

Medium Risk
Touches investigation streaming, MCP handshake signaling, and live UI state; race handling is new but heavily tested—no auth or data-model changes.

Overview
Investigation Activity now surfaces real startup progress before the first agent message, instead of a generic “Starting investigationâ€Ķ” line.

The backend emits new phase stream events: investigating, then connected when the agent completes MCP handshake on the private investigation mount (via investigationrefs.MarkConnected + middleware on successful initialize/tools/list), then ready from the CLI’s system/init line with model, Radar tool count (mcp__radar__ prefix), and MCP server statuses. Events are mutex-serialized so handshake and parser callbacks don’t interleave; the handshake watcher still fires once on short turns. Cursor maps init to ready with model only.

The UI folds phases into turn.startup (mergeStartupSignal keeps the furthest phase), shows agent-specific pending labels while the turn is running and the transcript is empty, and warns when MCP servers aren’t connected (warning persists on replay). Tool rows hide sub-2s durations on the collapsed row (seconds + exact ms in expanded header), show inline failure reasons, and move the elapsed timer to a separate “Ns elapsed” figure beside the status label.

Reviewed by Cursor Bugbot for commit 8d78513. Bugbot is set up for automated code reviews on this repo. Configure here.

@nadaverell
nadaverell requested a review from hisco as a code owner September 8, 2026 08:53
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Show observable startup progress and richer tool activity

âœĻ Enhancement 🧊 Tests 🕐 40+ Minutes

Grey Divider

AI Description

â€Ē Reports MCP connection and agent readiness before the first agent message.
â€Ē Surfaces unavailable MCP servers as replayable warnings without aborting investigations.
â€Ē Refines tool rows with meaningful durations, exact timing, and inline failure reasons.
Diagram

sequenceDiagram
    participant CLI as Agent CLI
    participant MCP as Private MCP
    participant Registry as Scope Registry
    participant Runner as Diagnoser
    participant View as Investigation View
    participant Timeline as Activity Timeline
    CLI->>MCP: initialize or tools/list
    MCP->>Registry: mark scope connected
    Registry-->>Runner: connected signal
    Runner-->>View: connected phase
    CLI->>Runner: system init metadata
    Runner-->>View: ready phase
    View->>Timeline: merge startup state
    Timeline-->>Timeline: render progress or warning
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use agent stream events only
  • ➕ Avoids the registry connection channel and handshake watcher
  • ➕ Keeps all startup reporting within agent parsers
  • ➖ Cannot honestly report the earlier MCP handshake
  • ➖ Depends on agent-specific initialization formats
  • ➖ Cursor does not expose tool or MCP server details
2. Enforce strict backend phase ordering
  • ➕ Lets clients apply phases without monotonic merging
  • ➕ Produces a deterministic investigating-connected-ready sequence
  • ➖ Would require buffering or delaying ready events when init arrives first
  • ➖ Could hide valid progress when a handshake never occurs
  • ➖ Adds timeout and lifecycle complexity to the runner

Recommendation: Keep the PR's dual-source approach: the scope channel preserves the MCP-to-AI package boundary, while parser events retain agent-specific readiness facts. Monotonic frontend merging handles legitimate event races without delaying progress or inventing timer-based states.

Files changed (12) +848 / -19

Enhancement (7) +315 / -18
agent_cursor.goEmit Cursor readiness metadata from initialization events +6/-0

Emit Cursor readiness metadata from initialization events

â€Ē Adds Cursor model parsing and emits a ready phase from system/init events. Cursor readiness intentionally excludes unavailable tool and MCP server metadata.

internal/ai/agent_cursor.go

diagnoser.goPublish serialized handshake and agent readiness phases +73/-4

Publish serialized handshake and agent readiness phases

â€Ē Extends stream events with model, Radar tool count, and MCP server status metadata. Watches the investigation scope for a one-shot handshake signal, serializes concurrent emissions, and parses Claude Code initialization into a ready phase.

internal/ai/diagnoser.go

registry.goAdd one-shot connection signals to investigation scopes +34/-6

Add one-shot connection signals to investigation scopes

â€Ē Each live scope now exposes a Connected channel. MarkConnected closes that channel once while rejecting inactive or already-closed scopes.

internal/investigationrefs/registry.go

server.goMark private investigation scopes during MCP handshake +5/-0

Mark private investigation scopes during MCP handshake

â€Ē The investigation middleware marks its active scope connected when it receives initialize or tools/list. This exposes MCP readiness without introducing an AI package dependency.

internal/mcp/server.go

diagnose.tsType startup metadata in diagnosis stream events +12/-1

Type startup metadata in diagnosis stream events

â€Ē Adds MCP server status definitions and optional model, tool-count, and server-status fields to phase events.

web/src/api/diagnose.ts

InvestigationView.tsxFold startup phases into active investigation turns +10/-0

Fold startup phases into active investigation turns

â€Ē Processes phase events into each turn's startup state and passes the selected agent label into the Activity timeline.

web/src/components/diagnose/InvestigationView.tsx

parts.tsxRender startup progress and richer tool outcomes +175/-7

Render startup progress and richer tool outcomes

â€Ē Adds monotonic startup state merging, readiness labels, and replayable MCP connection warnings. Tool rows now hide durations below two seconds, round longer durations, retain exact milliseconds in expanded results, and show producer failure reasons inline.

web/src/components/diagnose/parts.tsx

Tests (5) +533 / -1
agent_cursor_test.goVerify Cursor ready-phase model reporting +8/-1

Verify Cursor ready-phase model reporting

â€Ē Extends the pinned Cursor stream-format test to assert that system/init emits the resolved model in a ready phase.

internal/ai/agent_cursor_test.go

diagnoser_test.goCover readiness parsing and handshake race conditions +186/-0

Cover readiness parsing and handshake race conditions

â€Ē Tests Claude Code initialization metadata, Radar tool counting, event serialization, duplicate handshakes, and handshakes racing with short stream completion.

internal/ai/diagnoser_test.go

registry_test.goTest active-scope handshake signaling +29/-0

Test active-scope handshake signaling

â€Ē Verifies connection signals are initially open, close idempotently for active scopes, and reject inactive or closed scopes.

internal/investigationrefs/registry_test.go

server_investigation_test.goVerify private-mount handshake isolation +53/-0

Verify private-mount handshake isolation

â€Ē Confirms private investigation handshakes mark the scope while public mounts do not. Also covers accepted handshake methods, unrelated calls, and missing scope context.

internal/mcp/server_investigation_test.go

parts.test.tsxTest startup statuses, warnings, and tool-row details +257/-0

Test startup statuses, warnings, and tool-row details

â€Ē Covers monotonic startup phase merging, live versus replay rendering, MCP failure warnings, duration thresholds, exact timing details, and inline producer error extraction.

web/src/components/diagnose/parts.test.tsx

@qodo-code-review

qodo-code-review Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Startup alerts bypass shared UI ✗ Dismissed 📘 Rule violation ⚙ Maintainability
Description
Timeline builds an MCP connection warning from a custom status div, icon, and nested text
instead of AlertBanner, ProblemAlerts, or ConditionsSection. A failed or unavailable MCP
server activates this hand-rolled alert in the main Activity pane and duplicates shared warning
layout behavior.
Code

web/src/components/diagnose/parts.tsx[R1195-1198]

+      {failedServers.length > 0 && (
+        <div
+          role="status"
+          className="flex items-start gap-1.5 rounded border border-amber-500/40 bg-amber-500/10 p-2 text-[11px] leading-snug text-theme-text-secondary"
Relevance

●●● Strong

Explicit component-compliance rule directly applies, and accepted history favors fixing duplicated
user-facing warning behavior.

PR-#715

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 3036704 requires user-facing warnings to use a standard alert component, while the
added MCP warning constructs its own div, AlertTriangle, and text layout.

Rule 3036704: Use standard problem and label selector components in renderers
web/src/components/diagnose/parts.tsx[1195-1218]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Replace the custom MCP startup warning layout with one of the standard problem or alert components.

## Issue Context
The warning is user-facing UI in the main Activity pane, so its icon, semantics, and presentation should come from the shared alert system while preserving the detailed server-status message.

## Fix Focus Areas
- web/src/components/diagnose/parts.tsx[1195-1218]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Failed handshakes appear connected ✓ Resolved 🐞 Bug ≡ Correctness
Description
investigationEvidenceReferenceMiddleware calls MarkConnected before next successfully
processes an initialize or tools/list request, closing the scope channel even when the MCP
handler rejects or fails the request. DiagnoseStream treats that closure as authoritative and
emits the connected phase, causing the Activity pane to render “Connected to Radar's tools” even
though the handshake never established usable MCP tools.
Code

internal/mcp/server.go[R89-92]

+			if method == "initialize" || method == "tools/list" {
+				if scope, _ := ctx.Value(investigationEvidenceScopeKey{}).(string); scope != "" {
+					refs.MarkConnected(scope)
+				}
Relevance

●●● Strong

Connection is marked before handler success, creating a clear false-positive handshake state; MCP
correctness fixes are accepted.

PR-#658

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The middleware closes the investigation scope's connection signal before obtaining the downstream
handler result, even though that call can return an error. The registry and diagnoser treat the
channel closure as unconditional proof of connection, and the timeline maps the resulting `phase:
connected` event directly to the connected-tools label.

internal/mcp/server.go[89-96]
internal/ai/diagnoser.go[587-605]
web/src/components/diagnose/parts.tsx[498-511]
internal/mcp/server.go[86-96]
internal/investigationrefs/registry.go[71-84]
internal/ai/diagnoser.go[593-605]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description

Only mark and report the MCP connection after the handshake method has completed successfully. Move `MarkConnected` after `next` and invoke it only when the `initialize` or `tools/list` handler returns without error.

## Issue Context

The investigation middleware currently closes the scope's connection channel before delegating to the MCP handler. Because `DiagnoseStream` treats that closure as authoritative and immediately emits a connected startup phase, a rejected or failed `initialize` / `tools/list` request gives the user a false success signal even though usable MCP tools were not established. Update the relevant tests to cover unsuccessful handshake requests and verify that they do not mark the scope as connected.

## Fix Focus Areas

- internal/mcp/server.go[89-96]
- internal/mcp/server_investigation_test.go[276-315]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Startup warnings ignore the theme ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
Timeline renders the startup warning with the hardcoded bg-amber-500/10 background instead of an
approved theme background token. Whenever an MCP server is not connected, this non-themable color is
displayed in the Activity pane and can conflict with the active theme.
Code

web/src/components/diagnose/parts.tsx[1198]

+          className="flex items-start gap-1.5 rounded border border-amber-500/40 bg-amber-500/10 p-2 text-[11px] leading-snug text-theme-text-secondary"
Relevance

●●● Strong

Explicit theme-token rule directly prohibits the added hardcoded background utility.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 3036653 permits only the approved theme background utilities, while the new startup
warning uses bg-amber-500/10.

Rule 3036653: Use theme background tokens instead of hardcoded utility color classes
web/src/components/diagnose/parts.tsx[1195-1200]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Replace the hardcoded amber warning background with an approved theme background token.

## Issue Context
The startup warning currently uses `bg-amber-500/10`; background utilities must use the shared theme vocabulary.

## Fix Focus Areas
- web/src/components/diagnose/parts.tsx[1195-1200]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (4)
4. Hosted startup details disappear ✗ Dismissed 🔗 Cross-repo conflict ≡ Correctness
Description
Radar Hub's nodeLine and DiagnoseStreamEvent omit the new Model, ToolCount, and MCPServers
fields, and translateNodeLine reconstructs phase events using only Phase. When Hub owns
/api/diagnose/*, decoding and re-encoding the stream strips these values before the Radar UI
receives them, including the server status needed to render startup warnings.
Code

internal/ai/diagnoser.go[R190-192]

+	Model             string               `json:"model,omitempty"`
+	ToolCount         *int                 `json:"toolCount,omitempty"`
+	MCPServers        []MCPServerStatus    `json:"mcpServers,omitempty"`
Relevance

●●● Strong

Hosted proxy re-encoding can discard newly added fields; historical reviews accept fixes preventing
streamed metadata loss.

PR-#1081

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR adds and emits three ready-phase metadata fields, and its UI relies on mcpServers to
display connection warnings. Radar Hub explicitly replaces Radar's diagnosis backend in hosted mode,
but its decoder and translated event types contain no corresponding fields and rebuild phase events
with only the phase name, proving that the metadata is discarded.

internal/ai/diagnoser.go[185-192]
internal/ai/diagnoser.go[945-955]
web/src/components/diagnose/parts.tsx[1184-1216]
External repo: skyhook-dev/radar-hub, internal/server/ai_chat.go [25-32]
External repo: skyhook-dev/radar-hub, internal/ai/jobengine/types.go [62-88]
External repo: skyhook-dev/radar-hub, internal/ai/jobengine/types.go [103-150]
External repo: skyhook-dev/radar-hub, cmd/sidecar/main.go [137-154]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Radar Hub's hosted diagnosis pipeline drops `model`, `toolCount`, and `mcpServers` from `phase: ready` events. Extend its node decoder, stored event contract, and phase translation to preserve these optional fields.

## Issue Context
Hub intercepts the diagnosis API instead of forwarding it to the in-cluster Radar binary. Add a companion Radar Hub change and tests proving that ready-phase metadata survives node-stream decoding, persistence, replay, and SSE delivery; retain compatibility with events that omit the fields.

## Fix Focus Areas
- /cross_repos/radar-hub/internal/ai/jobengine/types.go[62-88]
- /cross_repos/radar-hub/internal/ai/jobengine/types.go[103-150]
- web/src/components/diagnose/parts.tsx[1184-1216]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Failure reasons ignore theme colors ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
ToolRow styles producer failure text with the hardcoded text-red-400 palette class instead of a
theme text token. Every collapsed failed tool row with a producer-supplied reason reaches this
styling, making its text color bypass theme-controlled contrast.
Code

web/src/components/diagnose/parts.tsx[R1483-1486]

+      {errorReason && !open && (
+        <span className="investigation-tool-reason min-w-0 flex-1 truncate text-[11px] text-red-400">
+          {errorReason}
+        </span>
Relevance

●●● Strong

Explicit theme text-color rule directly prohibits the added hardcoded red utility.

PR-#715

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 3036659 prohibits non-theme text palette utilities unless an exception is explicitly
documented, and the new failure-reason element directly uses text-red-400.

Rule 3036659: Use theme text color utility classes instead of hardcoded Tailwind gray classes
web/src/components/diagnose/parts.tsx[1483-1486]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Replace the hardcoded red failure-reason text utility with an appropriate semantic theme text token.

## Issue Context
The new inline tool failure reason currently uses `text-red-400`, and no documented exception permits bypassing theme text colors.

## Fix Focus Areas
- web/src/components/diagnose/parts.tsx[1483-1486]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Short turns hide tool connections ✓ Resolved 🐞 Bug ☞ Reliability
Description
The handshake watcher returns after one nonblocking connection check when streamDone wins the
select. Because streamDone closes before cmd.Wait and before the evidence scope is closed, a
successful handshake arriving in that interval is recorded by the registry but never emitted to the
run.
Code

internal/ai/diagnoser.go[R599-602]

+				select {
+				case <-connected:
+				default:
+					return
Relevance

●● Moderate

The channel shutdown race is plausible, but no close precedent specifically confirms this short-turn
watcher behavior.

PR-#1081

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The watcher abandons the channel immediately after its nonblocking default, but the registry remains
active until after cmd.Wait, allowing a later valid MarkConnected call that nobody observes.

internal/ai/diagnoser.go[593-610]
internal/ai/diagnoser.go[613-618]
internal/investigationrefs/registry.go[73-84]
internal/mcp/server.go[89-94]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A connection can arrive after stdout reaches EOF but before the agent process exits, after the watcher has already returned. Keep the watcher alive until the process has completed or the evidence scope is closed, while still ensuring all emitted phases precede `DiagnoseStream` returning.

## Issue Context
The current one-time recheck only handles channels that are already simultaneously ready; it does not close the race window between that check and `cmd.Wait`.

## Fix Focus Areas
- internal/ai/diagnoser.go[585-618]
- internal/ai/diagnoser_test.go[1083-1109]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Pending servers look unavailable ✓ Resolved 🐞 Bug ≡ Correctness
Description
Timeline appends that the agent ran without an external server's tools for every non-connected
status, although mcpStatusIsFailure explicitly classifies only failed and needs-auth as definite
failures. In full-local runs where user MCP servers can report pending or another non-final state,
the replayable warning asserts those tools were absent for the whole turn without evidence of that
outcome.
Code

web/src/components/diagnose/parts.tsx[R1215-1216]

+                : ` at startup — Radar's tools may not have been available to ${agentLabel} this turn.`
+              : ` at startup — ${agentLabel} ran this turn without those tools.`}
Relevance

●● Moderate

The wording may overstate pending or unknown states, but this is a subjective product-semantics
correction.

PR-#903

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code warns for every status other than connected and acknowledges that pending or unknown values
are not definite outcomes, yet the non-Radar suffix states categorically that the turn ran without
those tools. Full-local execution can include user-configured MCP servers, making that branch
reachable for servers beyond Radar.

web/src/components/diagnose/parts.tsx[1184-1216]
web/src/components/diagnose/parts.tsx[1376-1387]
internal/ai/agent_claude.go[12-15]
internal/ai/agent_claude.go[34-52]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Non-final MCP startup states are rendered with a definitive claim that their tools were unavailable for the entire turn. Use definite wording only for known failure states and conditional wording for pending or unknown states.

## Issue Context
Full-local Claude runs merge Radar into the user's MCP setup, so the warning can include external servers as well as Radar.

## Fix Focus Areas
- web/src/components/diagnose/parts.tsx[1184-1216]
- web/src/components/diagnose/parts.tsx[1376-1387]
- web/src/components/diagnose/parts.test.tsx[439-469]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 41 rules
✅ Cross-repo context — repo relationships
  Explored: repo: skyhook-dev/radar-hub (sha: fc87cab9)
  Explored: repo: skyhook-dev/radar-hub-web (sha: 15b7a793)
Review mode: 🧠 Deep: This is a broad, behavior-changing cross-layer PR spanning concurrent startup signaling, MCP lifecycle state, stream parsing, replay/UI folding, and tool error rendering, with many independent logic sites where redundant review could catch subtle defects.

Grey Divider

Tip of the day
ðŸ’Ą Did you know, you can commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread web/src/components/diagnose/parts.tsx Outdated
Comment thread web/src/components/diagnose/parts.tsx
Comment thread web/src/components/diagnose/parts.tsx Outdated
Comment thread internal/ai/diagnoser.go
Comment thread web/src/components/diagnose/parts.tsx Outdated
Comment thread internal/mcp/server.go
Comment thread internal/ai/diagnoser.go
@nadaverell

Copy link
Copy Markdown
Contributor Author

Triage of the Qodo review (head db571530):

# Finding Verdict Where
1 Startup warning uses hardcoded bg-amber-500/10 Fixed border-semantic-warning/40 bg-semantic-warning/10, icon text-semantic-warning (5e597c2d)
2 Failure reason uses text-red-400 Fixed text-semantic-error (5e597c2d)
3 Startup warning should use AlertBanner Skipped AlertBanner is the drawer-scale renderer surface (mb-4 p-3, 14px title + message). The Activity pane is an 11px transcript and every warning in parts.tsx (consent, apply, truncation notes) is hand-rolled at that scale; the banner would be out of register there.
4 Watcher can miss a handshake between stdout EOF and process exit Fixed The watcher now stays alive until cmd.Wait() returns and still emits before DiagnoseStream returns (5e597c2d)
5 Pending external servers get definite "ran without those tools" wording Fixed Definite wording only when every non-connected server is failed/needs-auth; otherwise "may not have been available"; test added (5e597c2d)
6 MarkConnected fired before next, so a rejected handshake read as connected Fixed Marked only after initialize/tools/list returns without error; middleware test covers rejected initialize and failed tools/list (5e597c2d)
7 Radar Hub's hosted transport forwards only the phase name No code change The UI already renders identically without the init facts: a bare " ready" label and no warning. Pinned by the "bare ready label" test; no fallback added, per the no-defensive-shape rule.

Bugbot: passed with no findings. Also in db571530, from the live capture pass: the elapsed counter is a row-level "Ns elapsed" figure rather than a suffix on each startup label, and a collapsed failed row now lets the arguments shrink first, drops Radar's retry hints after the em dash, and middle-truncates a long reason so its identifier tail survives.

The Activity pane showed only "Starting investigationâ€Ķ" and a timer for
the 20+ seconds until the agent's first message, although three real
signals exist before it. Report the two that are observable:

- "Connected to Radar's tools": the private investigation mount marks the
  turn's evidence scope connected on the MCP handshake (initialize or
  tools/list). The runner watches the scope and emits one `connected`
  phase per turn. internal/mcp still does not import internal/ai; the
  seam is a channel on the investigationrefs scope.
- "<agent> ready · <model> · <n> Radar tools": Claude Code's stream-json
  system/init line carries the model, the tool list and each MCP server's
  status. It becomes a `ready` phase with Model, ToolCount and MCPServers.
  Cursor's init names the model only. A server that is not connected
  renders as a warning in the Activity pane; it never aborts the run.

The pending status line shows the latest startup phase while the first
turn has no items, keeps the elapsed timer, yields to tool activity once
items arrive, and never renders on a replayed finished turn. Phase events
were already persisted in the run log; the new fields are optional.

Tool rows: hide durations under 2s on the collapsed row and show whole
seconds above it, keep the exact milliseconds in the expanded result
header, and show the producer's failure reason inline on a failed row.
Mark the investigation scope connected after initialize or tools/list
returns without error, not before, so a rejected handshake never renders
as connected. Keep the handshake watcher alive until the agent process
exits, so a handshake landing between stdout EOF and exit is still
reported before the turn returns.

Use the semantic warning and error tokens for the startup warning and the
inline failure reason, and only claim a server's tools were absent when
the CLI reported a definite failure; pending states get conditional
wording. A ready phase without init facts renders the bare ready label.
â€Ķentifiers

The running status appended the turn's elapsed seconds to each startup
label, which read as the duration of that phase. The counter is now a
row-level "Ns elapsed" figure set apart from the label.

On a collapsed failed tool row the arguments now give way before the
reason, Radar's retry hints after the em dash are dropped, and a long
reason is middle-truncated so the identifier at its tail survives.
@nadaverell
nadaverell force-pushed the feat/investigation-startup-signals branch from db57153 to 8d78513 Compare September 9, 2026 21:55
@nadaverell
nadaverell merged commit 9f73370 into main Sep 9, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant