Skip to content

fix(terminal): make periodic refreshes non-blocking - #264

Merged
omarluq merged 4 commits into
mainfrom
fix/non-blocking-terminal-refresh
Aug 10, 2026
Merged

fix(terminal): make periodic refreshes non-blocking#264
omarluq merged 4 commits into
mainfrom
fix/non-blocking-terminal-refresh

Conversation

@omarluq

@omarluq omarluq commented Aug 10, 2026

Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 39b485c8-0a04-4302-b719-bbededf08251

📥 Commits

Reviewing files that changed from the base of the PR and between 887ce6a and b7d5f0c.

📒 Files selected for processing (2)
  • internal/terminal/refresh.go
  • internal/terminal/refresh_acceptance_internal_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/terminal/refresh_acceptance_internal_test.go
  • internal/terminal/refresh.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added responsive background refreshes for terminal, agent-task, and workflow panels.
    • Panels now reuse snapshots and update asynchronously without blocking the interface.
    • Added refresh coalescing to avoid redundant updates and duplicate data requests.
  • Bug Fixes

    • Prevented stale data from appearing after session changes.
    • Preserved available panel data when individual refreshes fail.
    • Improved handling of task updates, watch failures, cancellations, and completed tasks.
    • Kept workflow and task details consistent with the active session.
    • Improved refresh reliability during slow or interrupted operations.
    • Reduced duplicate data loading and improved panel responsiveness.

Walkthrough

The change adds asynchronous, coalesced terminal refreshes backed by immutable snapshots. It updates agent-task and workflow panels to use snapshots, resets state across session changes, and adds timing diagnostics for terminal and extension dispatch operations.

Changes

Terminal refresh and diagnostics

Layer / File(s) Summary
Refresh pipeline and snapshot loading
internal/terminal/refresh.go, internal/terminal/terminal_refresh_data.go, internal/terminal/refresh_acceptance_internal_test.go
The application loads independent sections asynchronously, coalesces requests, rejects stale results, preserves failed sections, and applies valid snapshots.
Refresh wiring and session state
internal/terminal/app.go, internal/terminal/async_events.go, internal/terminal/agent_tasks.go, internal/terminal/*session*, internal/terminal/prompt_response.go, internal/terminal/agent_tasks_behavior_internal_test.go
Events and periodic work request refreshes. Session changes invalidate refresh state. Agent-task panels and completion handling use snapshots.
Snapshot-backed workflow panels
internal/terminal/workflows.go, internal/terminal/workflows_internal_test.go
Workflow panels and details use validated snapshots instead of runtime queries.
Extension dispatch diagnostics
internal/extension/manager.go, internal/extension/manager_dispatch.go, internal/extension/manager_timer.go, internal/extension/manager_diagnostics_internal_test.go
Tick, render, and timer dispatches record timing and outcomes. Slow warnings use rate limiting and suppressed-count reporting without private event content.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant RefreshCoordinator
  participant RuntimeControllers
  participant InterruptHandler
  App->>RefreshCoordinator: request terminal refresh
  RefreshCoordinator->>RuntimeControllers: load agent and workflow sections
  RuntimeControllers-->>RefreshCoordinator: return snapshot sections
  RefreshCoordinator-->>App: publish terminalRefreshResult
  App->>InterruptHandler: process refresh result
  InterruptHandler->>App: apply valid snapshot
Loading

Possibly related PRs

Poem

A rabbit watched snapshots flow,
Refresh requests merged in rows.
Tasks used cached state with care,
Slow logs marked the work they share.
Sessions cleared stale trails,
Diagnostics counted details.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided, so the changes and implementation scope are not documented. Add a concise description of the non-blocking refresh pipeline, snapshot-based loading, coalescing, cancellation, and related tests.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: making terminal periodic refreshes non-blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/non-blocking-terminal-refresh

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.56275% with 132 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.33%. Comparing base (64753cf) to head (b7d5f0c).

Files with missing lines Patch % Lines
internal/terminal/terminal_refresh_data.go 79.02% 54 Missing and 15 partials ⚠️
internal/terminal/refresh.go 76.75% 37 Missing and 6 partials ⚠️
internal/terminal/agent_tasks.go 84.90% 3 Missing and 5 partials ⚠️
internal/terminal/workflows.go 87.17% 3 Missing and 2 partials ⚠️
internal/terminal/async_events.go 75.00% 2 Missing ⚠️
internal/terminal/commands.go 0.00% 2 Missing ⚠️
internal/terminal/session_panel.go 0.00% 2 Missing ⚠️
internal/terminal/app.go 98.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #264      +/-   ##
==========================================
+ Coverage   84.31%   84.33%   +0.01%     
==========================================
  Files         335      337       +2     
  Lines       33942    34496     +554     
==========================================
+ Hits        28619    29093     +474     
- Misses       3628     3688      +60     
- Partials     1695     1715      +20     
Flag Coverage Δ
unittests 84.33% <82.56%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (11)
internal/terminal/refresh_acceptance_internal_test.go (2)

467-468: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Read listCalls through a mutex-guarded accessor.

agentTaskControllerStub.List increments listCalls under stub.mu, but Line 467 and Line 484 read the field directly. The read is safe today because the load completed before this point, however applyTerminalRefreshSnapshot starts watch goroutines against the same stub. Add a calls() accessor to agentTaskControllerStub, as refreshToolTaskController already provides, and use it here.

🤖 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 `@internal/terminal/refresh_acceptance_internal_test.go` around lines 467 -
468, Update agentTaskControllerStub with a mutex-guarded calls() accessor for
listCalls, matching refreshToolTaskController, and replace the direct listCalls
reads in the affected refresh test flow with this accessor while preserving the
existing toolCalls handling.

376-385: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

The test loader reads App state from the worker goroutine.

loadTerminalRefreshSnapshot is documented in internal/terminal/terminal_refresh_data.go (Line 87-88) as not reading App. This stub loader reads app.sessionID inside the worker goroutine, which breaks that contract and can report a race under go test -race if a later test variant writes sessionID while a refresh is in flight. Use request.SessionID, which is captured on the UI thread.

♻️ Proposed adjustment
-	app.refreshLoader = func(context.Context, *terminalRefreshRequest) terminalRefreshSnapshot {
-		snapshot := newTerminalRefreshSnapshot(app.sessionID)
+	app.refreshLoader = func(_ context.Context, request *terminalRefreshRequest) terminalRefreshSnapshot {
+		snapshot := newTerminalRefreshSnapshot(request.SessionID)
🤖 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 `@internal/terminal/refresh_acceptance_internal_test.go` around lines 376 -
385, Update the refreshLoader stub to use request.SessionID when constructing
the terminalRefreshSnapshot instead of reading app.sessionID from the worker
goroutine. Preserve the existing snapshot contents and loader behavior.
internal/extension/manager_dispatch.go (2)

240-247: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid the per-dispatch attribute allocation when debug logging is disabled.

logDispatchDuration runs on every tick and render dispatch. The attributes slice is allocated and filled before the log level is evaluated, so the allocation happens on each frame even when the handler discards debug records. Check the level first, or build the slice only when it is needed.

♻️ Proposed adjustment
-	attributes := make([]any, 0, extensionDiagnosticAttributes)
-	attributes = append(attributes,
-		slog.String("operation", operation),
-		slog.Duration("duration", duration),
-		slog.String("outcome", outcome),
-		slog.Int("count", count),
-	)
-	manager.logger.Debug("extension callback dispatch", attributes...)
-
-	if duration < extensionSlowDispatchThreshold {
-		return
-	}
+	slow := duration >= extensionSlowDispatchThreshold
+	if !slow && !manager.logger.Enabled(context.Background(), slog.LevelDebug) {
+		return
+	}
+
+	attributes := make([]any, 0, extensionDiagnosticAttributes)
+	attributes = append(attributes,
+		slog.String("operation", operation),
+		slog.Duration("duration", duration),
+		slog.String("outcome", outcome),
+		slog.Int("count", count),
+	)
+	manager.logger.Debug("extension callback dispatch", attributes...)
+
+	if !slow {
+		return
+	}

As per coding guidelines: "Keep the default render path hot and allocation-conscious".

🤖 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 `@internal/extension/manager_dispatch.go` around lines 240 - 247, Update
logDispatchDuration so it checks whether debug logging is enabled before
allocating or populating the attributes slice. Return or skip the logging work
when the logger discards Debug records, while preserving the existing extension
callback dispatch message and attributes when enabled.

Source: Coding guidelines


86-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add parentheses to the mixed boolean condition.

Go binds && tighter than ||, so the condition is correct. Parentheses make the intent explicit and prevent a wrong edit later.

♻️ Proposed adjustment
-	if event == nil || event.Name != "tick" && event.Name != "render" {
+	if event == nil || (event.Name != "tick" && event.Name != "render") {
🤖 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 `@internal/extension/manager_dispatch.go` around lines 86 - 88, Update the
condition in the event dispatch logic to parenthesize the combined event-name
check, explicitly grouping the `event.Name != "tick"` and `event.Name !=
"render"` comparisons under the existing nil check while preserving the current
behavior.
internal/extension/manager_diagnostics_internal_test.go (1)

145-168: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make the fake clock fail fast instead of blocking.

times holds exactly two values. If the dispatch path calls dispatchNow a third time, the receive at Line 162 blocks until the package test timeout, and the failure reason is not visible. Add a default branch that returns a sentinel time, so an unexpected call count fails the test quickly.

♻️ Proposed adjustment
 	manager.dispatchNow = func() time.Time {
-		now := <-times
+		var now time.Time
+		select {
+		case now = <-times:
+		default:
+			return startedAt.Add(time.Hour)
+		}
+
 		if now.Equal(startedAt) {
 			close(started)
 		}
🤖 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 `@internal/extension/manager_diagnostics_internal_test.go` around lines 145 -
168, Update the fake clock in assertDispatchIncludesLockWait so
manager.dispatchNow fails fast when times is exhausted instead of blocking. Add
a non-blocking default path that returns a sentinel time for unexpected calls,
while preserving the existing queued timestamps and started signaling behavior.
internal/terminal/workflows_internal_test.go (2)

307-343: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore coverage for the rejected-run cases.

The previous table-driven cases for a missing run and a run owned by another session were removed. openWorkflowDetail now validates against the snapshot, so those rules still apply and are still worth asserting. TestOpenWorkflowDetailRequiresSnapshot covers only the empty-snapshot case and the missing-detail case. Add a case where the snapshot holds a run whose Task.OwnerSessionID differs from app.sessionID. Prefer a table for these variants.

The getFails and linksFail fields on workflowPanelInspector may now be unreferenced after the removal. Remove them if no test sets them.

As per coding guidelines: "Prefer table-driven tests for core behavior".

Run the following script to check whether the stub failure flags are still used:

#!/bin/bash
# Description: Find remaining uses of the workflowPanelInspector failure flags.
set -euo pipefail

rg -n -C 3 '\b(getFails|linksFail)\b' internal/terminal
🤖 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 `@internal/terminal/workflows_internal_test.go` around lines 307 - 343, Extend
TestOpenWorkflowDetailRequiresSnapshot with table-driven cases covering an empty
snapshot and a snapshot containing a run whose Task.OwnerSessionID differs from
app.sessionID, preserving the expected errors and ensuring no detail lookup
occurs. Search workflowPanelInspector usages for getFails and linksFail, and
remove those fields if no tests still configure them.

Source: Coding guidelines


129-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fail the test when loadWorkflowDetails returns an error.

seedWorkflowPanelSnapshot discards the error and leaves workflowDetailSnapshotValid false. A seeding failure then surfaces as an unrelated assertion failure in the calling test. Accept *testing.T, use t.Context(), and require no error.

♻️ Proposed adjustment
-func seedWorkflowPanelSnapshot(app *App, stub *workflowPanelInspector) {
+func seedWorkflowPanelSnapshot(t *testing.T, app *App, stub *workflowPanelInspector) {
+	t.Helper()
+
 	if stub == nil || stub.run == nil {
 		return
 	}
 
 	app.workflowPanelSnapshot = []database.WorkflowRunEntity{*stub.run}
 	app.workflowPanelSnapshotValid = true
 
-	details, err := loadWorkflowDetails(context.Background(), stub, []string{stub.run.Task.ID})
-	if err == nil {
-		app.workflowProgress = details.ProgressByRun
-		app.workflowSteps = details.StepsByRun
-		app.workflowDetailSnapshotValid = true
-	}
+	details, err := loadWorkflowDetails(t.Context(), stub, []string{stub.run.Task.ID})
+	require.NoError(t, err)
+
+	app.workflowProgress = details.ProgressByRun
+	app.workflowSteps = details.StepsByRun
+	app.workflowDetailSnapshotValid = true
 }

Update the five call sites at Lines 174, 204, 245, 256, and 286, and Line 315, to pass t.

🤖 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 `@internal/terminal/workflows_internal_test.go` around lines 129 - 143, Update
seedWorkflowPanelSnapshot to accept *testing.T, use t.Context() for
loadWorkflowDetails, and fail immediately with a test assertion when it returns
an error instead of silently skipping snapshot initialization. Update all six
call sites in the affected tests to pass t.
internal/terminal/agent_tasks_behavior_internal_test.go (1)

555-575: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a timeout to applyAgentRefreshInterrupt.

The loop blocks on app.screen.EventQ() with no deadline. If a refresh result is never posted, the test hangs until the package timeout and the failure reason is not reported. awaitInterrupt in internal/terminal/refresh_acceptance_internal_test.go already implements a bounded wait in the same package. Reuse it or add an equivalent time.After branch.

♻️ Proposed adjustment
 	for {
-		event := <-app.screen.EventQ()
-
-		interrupt, ok := event.(*tcell.EventInterrupt)
-		if !ok {
-			continue
-		}
+		var event tcell.Event
+
+		select {
+		case event = <-app.screen.EventQ():
+		case <-time.After(time.Second):
+			t.Fatal("agent refresh interrupt was not published")
+		}
+
+		interrupt, ok := event.(*tcell.EventInterrupt)
+		if !ok {
+			continue
+		}
🤖 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 `@internal/terminal/agent_tasks_behavior_internal_test.go` around lines 555 -
575, Update applyAgentRefreshInterrupt to use the bounded-wait behavior from
awaitInterrupt, or an equivalent time.After/select timeout, while waiting for
app.screen.EventQ(). Ensure a missing terminalRefreshResult fails promptly with
a clear timeout error instead of blocking indefinitely.
internal/terminal/app.go (1)

352-377: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the duplicated scopedOrder and scopedEnabled initialization.

initializeAppViewState sets both fields at Lines 353-354. initializeAppInputState sets them again at Lines 375-376. Keep one owner for these fields.

♻️ Proposed cleanup
 	app.composerImages = []imageAttachment{}
 	app.bracketedPaste = false
-	app.scopedOrder = []string{}
-	app.scopedEnabled = map[string]bool{}
 }
🤖 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 `@internal/terminal/app.go` around lines 352 - 377, Remove the duplicate
scopedOrder and scopedEnabled assignments from initializeAppInputState, keeping
their initialization in initializeAppViewState as the single owner.
internal/terminal/agent_tasks.go (1)

703-715: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Collapse the duplicated branch.

Both paths call app.stopAgentTaskWatch(taskID) and return. The if len(app.agentTaskSessionStack) > 0 block no longer changes behavior.

♻️ Proposed simplification
 func (app *App) handleAgentTaskWatchError(_ context.Context, taskID, message string) {
 	app.addSystemMessage(message)
 
-	if len(app.agentTaskSessionStack) > 0 {
-		app.stopAgentTaskWatch(taskID)
-
-		return
-	}
-
 	// Leave the failed watch stopped. The next periodic snapshot can retry it
 	// without recursively publishing another event from this handler.
 	app.stopAgentTaskWatch(taskID)
 }
🤖 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 `@internal/terminal/agent_tasks.go` around lines 703 - 715, Collapse
handleAgentTaskWatchError by removing the redundant agentTaskSessionStack
conditional and invoking app.stopAgentTaskWatch(taskID) once after
app.addSystemMessage(message). Preserve the existing stop behavior without the
unnecessary early return or branch-specific comment.
internal/terminal/refresh.go (1)

128-132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the recover block.

The named panicValue variable is unused. SonarCloud flags it. Also add a short comment that states why the panic is swallowed, because an empty recover hides real defects.

♻️ Proposed simplification
-	defer func() {
-		if panicValue := recover(); panicValue != nil {
-			return
-		}
-	}()
+	// The screen event queue can be closed during shutdown; a send then panics.
+	defer func() { _ = recover() }()
🤖 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 `@internal/terminal/refresh.go` around lines 128 - 132, Update the deferred
recovery block in the refresh logic to call recover without assigning its
result, removing the unused panicValue variable. Add a brief comment explaining
why panics are intentionally swallowed in this path.

Source: Linters/SAST tools

🤖 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 `@internal/terminal/agent_tasks_behavior_internal_test.go`:
- Around line 1646-1649: Update the refresh apply path used by
app.refreshAgentTasksPanel so that when terminalRefreshSnapshot.hasErrors() is
true, it sets app.statusMessage to a user-visible error message while retaining
stale section data. Ensure partial_error continues to diagnostics but persistent
database failures are also surfaced through the status message.

In `@internal/terminal/agent_tasks.go`:
- Around line 1105-1126: Update deliverKnownAgentTaskCompletionText and its
callers so workflow-child detection still uses the in-memory
isTrackedWorkflowChild check before invoking finishAgentTaskCompletion. Preserve
the optimization that avoids isPersistedWorkflowChild on this completion path,
but ensure workflow children linked through workflow tables are suppressed and
do not call deliverAgentTaskCompletions.

In `@internal/terminal/terminal_refresh_data.go`:
- Around line 510-512: Make the refresh ordering deterministic in the append
loops over activeByID and listed by sorting entries with a stable task key such
as Task.ID or Task.CreatedAt before appending. Preserve the existing collection
contents while ensuring newly discovered agent tasks and workflow runs retain
consistent UI positions across refreshes.
- Around line 556-576: The missing-lookup branch in the task refresh logic
should retain tasks that are absent from the snapshot. Update the !found
handling for lookups.Value[previous.Task.ID] to return previous, nil instead of
removing the task, and preserve the existing behavior for nil and terminal task
states.

In `@internal/terminal/workflows.go`:
- Around line 113-129: Preserve the context in openWorkflowDetail and pass it
into openWorkflowDetailFromSnapshot. When workflowDetailSnapshotValid is false,
request a workflow refresh using that context before returning the loading
error, so the detail view is reopened after the next snapshot and
workflowPanelRunID is populated.

---

Nitpick comments:
In `@internal/extension/manager_diagnostics_internal_test.go`:
- Around line 145-168: Update the fake clock in assertDispatchIncludesLockWait
so manager.dispatchNow fails fast when times is exhausted instead of blocking.
Add a non-blocking default path that returns a sentinel time for unexpected
calls, while preserving the existing queued timestamps and started signaling
behavior.

In `@internal/extension/manager_dispatch.go`:
- Around line 240-247: Update logDispatchDuration so it checks whether debug
logging is enabled before allocating or populating the attributes slice. Return
or skip the logging work when the logger discards Debug records, while
preserving the existing extension callback dispatch message and attributes when
enabled.
- Around line 86-88: Update the condition in the event dispatch logic to
parenthesize the combined event-name check, explicitly grouping the `event.Name
!= "tick"` and `event.Name != "render"` comparisons under the existing nil check
while preserving the current behavior.

In `@internal/terminal/agent_tasks_behavior_internal_test.go`:
- Around line 555-575: Update applyAgentRefreshInterrupt to use the bounded-wait
behavior from awaitInterrupt, or an equivalent time.After/select timeout, while
waiting for app.screen.EventQ(). Ensure a missing terminalRefreshResult fails
promptly with a clear timeout error instead of blocking indefinitely.

In `@internal/terminal/agent_tasks.go`:
- Around line 703-715: Collapse handleAgentTaskWatchError by removing the
redundant agentTaskSessionStack conditional and invoking
app.stopAgentTaskWatch(taskID) once after app.addSystemMessage(message).
Preserve the existing stop behavior without the unnecessary early return or
branch-specific comment.

In `@internal/terminal/app.go`:
- Around line 352-377: Remove the duplicate scopedOrder and scopedEnabled
assignments from initializeAppInputState, keeping their initialization in
initializeAppViewState as the single owner.

In `@internal/terminal/refresh_acceptance_internal_test.go`:
- Around line 467-468: Update agentTaskControllerStub with a mutex-guarded
calls() accessor for listCalls, matching refreshToolTaskController, and replace
the direct listCalls reads in the affected refresh test flow with this accessor
while preserving the existing toolCalls handling.
- Around line 376-385: Update the refreshLoader stub to use request.SessionID
when constructing the terminalRefreshSnapshot instead of reading app.sessionID
from the worker goroutine. Preserve the existing snapshot contents and loader
behavior.

In `@internal/terminal/refresh.go`:
- Around line 128-132: Update the deferred recovery block in the refresh logic
to call recover without assigning its result, removing the unused panicValue
variable. Add a brief comment explaining why panics are intentionally swallowed
in this path.

In `@internal/terminal/workflows_internal_test.go`:
- Around line 307-343: Extend TestOpenWorkflowDetailRequiresSnapshot with
table-driven cases covering an empty snapshot and a snapshot containing a run
whose Task.OwnerSessionID differs from app.sessionID, preserving the expected
errors and ensuring no detail lookup occurs. Search workflowPanelInspector
usages for getFails and linksFail, and remove those fields if no tests still
configure them.
- Around line 129-143: Update seedWorkflowPanelSnapshot to accept *testing.T,
use t.Context() for loadWorkflowDetails, and fail immediately with a test
assertion when it returns an error instead of silently skipping snapshot
initialization. Update all six call sites in the affected tests to pass t.
🪄 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: 389016f5-b603-4d42-bb7e-2d156d30bdf3

📥 Commits

Reviewing files that changed from the base of the PR and between aca2032 and 01a2fb2.

📒 Files selected for processing (17)
  • internal/extension/manager.go
  • internal/extension/manager_diagnostics_internal_test.go
  • internal/extension/manager_dispatch.go
  • internal/extension/manager_timer.go
  • internal/terminal/agent_tasks.go
  • internal/terminal/agent_tasks_behavior_internal_test.go
  • internal/terminal/app.go
  • internal/terminal/async_events.go
  • internal/terminal/commands.go
  • internal/terminal/prompt_response.go
  • internal/terminal/refresh.go
  • internal/terminal/refresh_acceptance_internal_test.go
  • internal/terminal/session_commands.go
  • internal/terminal/session_panel.go
  • internal/terminal/terminal_refresh_data.go
  • internal/terminal/workflows.go
  • internal/terminal/workflows_internal_test.go

Comment thread internal/terminal/agent_tasks_behavior_internal_test.go
Comment thread internal/terminal/agent_tasks.go
Comment thread internal/terminal/terminal_refresh_data.go Outdated
Comment thread internal/terminal/terminal_refresh_data.go
Comment thread internal/terminal/workflows.go Outdated
@omarluq
omarluq force-pushed the fix/non-blocking-terminal-refresh branch from 01a2fb2 to 5dcee13 Compare August 10, 2026 16:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/terminal/refresh.go (1)

292-305: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid debug work when debug logging is disabled.

terminalRefreshDiagnostics.log constructs attributes before the logger can filter a debug record. Every completed refresh logs the total operation, and timed sections add more calls. Gate debug attribute construction on logger.Enabled, then construct warning attributes only for slow operations.

Proposed change
 func (diagnostics *terminalRefreshDiagnostics) log(
 	operation string,
 	duration time.Duration,
 	outcome string,
 	count int,
 ) {
-	attributes := []any{
-		slog.String("operation", operation),
-		slog.Duration("duration", duration),
-		slog.String("outcome", outcome),
-	}
-	if count > 0 {
-		attributes = append(attributes, slog.Int("count", count))
-	}
-
 	logger := slog.Default()
-	logger.Debug("terminal refresh", attributes...)
+	if logger.Enabled(context.Background(), slog.LevelDebug) {
+		attributes := []any{
+			slog.String("operation", operation),
+			slog.Duration("duration", duration),
+			slog.String("outcome", outcome),
+		}
+		if count > 0 {
+			attributes = append(attributes, slog.Int("count", count))
+		}
+		logger.Debug("terminal refresh", attributes...)
+	}
 
 	if duration < terminalSlowOperationThreshold {
 		return
 	}
 
+	attributes := []any{
+		slog.String("operation", operation),
+		slog.Duration("duration", duration),
+		slog.String("outcome", outcome),
+	}
+	if count > 0 {
+		attributes = append(attributes, slog.Int("count", count))
+	}

As per coding guidelines, “Keep the default render path hot and allocation-conscious.”

🤖 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 `@internal/terminal/refresh.go` around lines 292 - 305, Update
terminalRefreshDiagnostics.log to obtain the logger and check logger.Enabled for
debug level before constructing the refresh attributes or emitting the debug
record. Preserve diagnostics for enabled debug logging, and defer construction
of warning attributes until after confirming duration meets
terminalSlowOperationThreshold, keeping the normal render path
allocation-conscious.

Source: Coding guidelines

🤖 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 `@internal/terminal/refresh_acceptance_internal_test.go`:
- Around line 213-219: Synchronize the canceled refresh test with completion of
the refresh goroutine before inspecting screen.EventQ(). Add or reuse a
completion signal for the refresh execution or screen publication path, wait for
that signal after awaitSignal, then assert that no event was published; keep the
existing cancellation and empty-queue assertions.

---

Nitpick comments:
In `@internal/terminal/refresh.go`:
- Around line 292-305: Update terminalRefreshDiagnostics.log to obtain the
logger and check logger.Enabled for debug level before constructing the refresh
attributes or emitting the debug record. Preserve diagnostics for enabled debug
logging, and defer construction of warning attributes until after confirming
duration meets terminalSlowOperationThreshold, keeping the normal render path
allocation-conscious.
🪄 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: d68841e3-7fa1-43c3-87c4-36dc04773de1

📥 Commits

Reviewing files that changed from the base of the PR and between 01a2fb2 and 5dcee13.

📒 Files selected for processing (12)
  • internal/extension/manager_diagnostics_internal_test.go
  • internal/extension/manager_dispatch.go
  • internal/terminal/agent_tasks.go
  • internal/terminal/agent_tasks_behavior_internal_test.go
  • internal/terminal/app.go
  • internal/terminal/async_events.go
  • internal/terminal/prompt_response.go
  • internal/terminal/refresh.go
  • internal/terminal/refresh_acceptance_internal_test.go
  • internal/terminal/terminal_refresh_data.go
  • internal/terminal/workflows.go
  • internal/terminal/workflows_internal_test.go
🚧 Files skipped from review as they are similar to previous changes (10)
  • internal/terminal/prompt_response.go
  • internal/terminal/async_events.go
  • internal/extension/manager_dispatch.go
  • internal/extension/manager_diagnostics_internal_test.go
  • internal/terminal/workflows.go
  • internal/terminal/terminal_refresh_data.go
  • internal/terminal/agent_tasks.go
  • internal/terminal/workflows_internal_test.go
  • internal/terminal/app.go
  • internal/terminal/agent_tasks_behavior_internal_test.go

Comment thread internal/terminal/refresh_acceptance_internal_test.go
@omarluq
omarluq force-pushed the fix/non-blocking-terminal-refresh branch from 5dcee13 to 887ce6a Compare August 10, 2026 16:50
@omarluq
omarluq force-pushed the fix/non-blocking-terminal-refresh branch from 887ce6a to b7d5f0c Compare August 10, 2026 17:15
@sonarqubecloud

Copy link
Copy Markdown

@omarluq
omarluq merged commit d8842ce into main Aug 10, 2026
15 checks passed
@omarluq
omarluq deleted the fix/non-blocking-terminal-refresh branch August 10, 2026 17:46
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