Skip to content

Add context preflight and manual compaction#82

Open
omarluq wants to merge 5 commits into
mainfrom
feat/context-preflight
Open

Add context preflight and manual compaction#82
omarluq wants to merge 5 commits into
mainfrom
feat/context-preflight

Conversation

@omarluq
Copy link
Copy Markdown
Owner

@omarluq omarluq commented Jun 4, 2026

Summary

  • add a context budget/preflight guard with configurable reserves
  • make compaction summaries model-facing while preserving retained tail context
  • implement manual /compact with async terminal spinner/cancellation handling

Validation

  • mise exec -- task ci

Notes

  • leaves unrelated docs/refactoring/*.md files uncommitted

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Conversation compaction: manual /compact, async "Compacting context..." indicator, preserved recent tail and generated summaries.
    • Context budgeting and preflight checks with configurable token reserves, safety margin, and recent-history retention.
    • Completions now respect a tool-execution toggle.
  • Bug Fixes

    • Better handling of incomplete provider responses due to output-token limits: preserve partial output and avoid blind retries.
  • Documentation

    • Expanded context-management guide with incomplete-response recovery guidance.

Walkthrough

Adds configurable context token budgeting and preflight checks, implements session compaction planning and summarization (disabled tools), normalizes model-facing messages, persists model-facing metadata and compaction entries, integrates budgeting into runtime model flow, and provides terminal command/state/rendering for manual compaction plus tests and docs.

Changes

Context Compaction System

Layer / File(s) Summary
Configuration & Token Budget Foundation
internal/config/config.go, internal/config/loader.go, internal/assistant/context_budget.go, internal/assistant/context_budget_test.go, config.example.yaml
Adds ContextConfig and defaults, implements contextBudget computation (output/provider/tool/safety reserves), UsageWithBudget() and Validate() preflight, plus tests for preflight, breakdowns, and validation.
Model-Facing Message Transformation
internal/assistant/messages.go, internal/assistant/anthropic.go, internal/assistant/openai_chat.go, internal/assistant/runtime_context.go, internal/assistant/runtime_context_internal_test.go
Refactors role mappings to handle branch/compaction summaries explicitly, adds modelFacingMessage to wrap summaries with core prefixes/suffixes, and broadens isModelFacingRole so summaries/custom/bash roles are included in model-facing context; tests assert preserved roles/contents.
Runtime Entry Persistence & Budget Integration
internal/assistant/runtime_entries.go, internal/assistant/runtime.go, internal/assistant/runtime_model.go, internal/assistant/context_build.go
Extracts appendUserPromptEntry/appendAssistantResponseEntry, adds responseBundle.ModelFacing, integrates context budgeting into modelResponse/ContextUsage, populates CompletionRequest with DisableTools defaults, and emits budgeted usage.
Compaction Planning & Execution
internal/assistant/context_compaction.go, internal/assistant/context_compaction_test.go
Implements Runtime.CompactSession/CompactSessionFrom, planCompaction cutting logic, message normalization for compaction input, summarizeCompaction using a completion request with tools disabled, appending compaction entries and metadata, plus unit/runtime tests validating behavior and edge cases.
Storage & Context Reconstruction
internal/database/session_store.go, internal/database/entry_metadata.go, internal/database/session_repository_test.go
Adds AppendMessageWithModelFacing, persists model-facing override into entry DataJSON, BuildContext treats EntryTypeCompaction specially via compaction-tail reconstruction, refactors applyEntryToContext into dispatch table, and updates tests to reflect multiple context messages.
Tool Schema & Retry Integration
internal/assistant/tool_schema.go, internal/assistant/retry.go, internal/assistant/client.go
CompletionRequest.DisableTools field added and honored (early-return from tool-definition lookup when true); context_window_exceeded added to non-retryable provider codes. Test fixtures set DisableTools: false where appropriate.
Terminal Compaction Commands & Lifecycle
internal/terminal/commands.go, internal/terminal/compact_commands.go, internal/terminal/compact_commands_test.go, internal/terminal/async_events.go
Wires /compact command, tracks activeCompaction and compacting state, starts asynchronous compaction work (runCompactSession), posts compact-done/error async events, handles events (apply/ignore stale), supports cancellation, and tests async start/done handling.
Terminal UI & Rendering Changes
internal/terminal/app.go, internal/terminal/input.go, internal/terminal/input_escape.go, internal/terminal/autocomplete.go, internal/terminal/prompt_cancel.go, internal/terminal/prompt_submit.go, internal/terminal/render*.go, internal/terminal/runtime_buffers.go, internal/terminal/extension_events.go
Considers compaction as busy(), blocks prompt submit during compaction, updates escape/autocomplete/queue logic, shows "Compacting context..." loader and compaction-specific shimmer palette, includes compacting flag in buffers and extension context, and updates rendering tests.
Docs & Examples
docs/context-management.md, config.example.yaml, internal/config/loader_test.go, assorted test fixtures
Adds documentation for incomplete-response (max_output_tokens) handling and recovery guidance, updates example config with context block, sets loader defaults for context values, and updates tests to assert context config values and fixture DisableTools initialization.

Sequence Diagram(s)

sequenceDiagram
  participant App as Terminal (compact)
  participant Runtime as Runtime.CompactSessionFrom
  participant Sessions as SessionRepository
  participant Planner as planCompaction
  participant Client as CompletionClient
  App->>Runtime: trigger CompactSessionFrom(sessionID, cwd, parent)
  Runtime->>Sessions: load branch entries
  Runtime->>Planner: compute compaction plan (summarize vs keep)
  Runtime->>Client: request summary (DisableTools=true, system prompt, messages)
  Client-->>Runtime: return summary text
  Runtime->>Sessions: AppendCompaction(summary, firstKeptEntryID, tokensBefore)
  Runtime->>App: return appended compaction entry
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • omarluq/librecode#32: Overlaps in Anthropic role-mapping changes at provider request/response mapping.
  • omarluq/librecode#45: Related to CompletionRequest shape and provider lifecycle request fields that interact with DisableTools.
  • omarluq/librecode#43: Related to tool-schema selection and request-aware tool-definition generation that this PR gates via DisableTools.

Poem

"🐰 I hopped through tokens, trimmed the rest,
Old turns summarized, the tail kept best,
Budgets in place, the compactor sings,
Requests respect limits — no infinite rings,
New prompts take flight on lighter wings."

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add context preflight and manual compaction' directly summarizes the main changes: introducing a context preflight guard and implementing manual compaction functionality.
Description check ✅ Passed The description is related to the changeset and explains the key objectives: context budget/preflight guards, model-facing compaction summaries, and manual /compact command with terminal UI.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/context-preflight

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

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Jun 4, 2026

Codecov Report

❌ Patch coverage is 79.87179% with 157 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.44%. Comparing base (cd27685) to head (dcad89a).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
internal/assistant/context_compaction.go 82.71% 30 Missing and 21 partials ⚠️
internal/database/session_store.go 64.93% 22 Missing and 5 partials ⚠️
internal/terminal/compact_commands.go 74.73% 14 Missing and 10 partials ⚠️
internal/assistant/context_budget.go 76.71% 11 Missing and 6 partials ⚠️
internal/config/config.go 27.27% 4 Missing and 4 partials ⚠️
internal/terminal/async_events.go 0.00% 6 Missing ⚠️
internal/assistant/anthropic.go 40.00% 3 Missing ⚠️
internal/assistant/messages.go 40.00% 3 Missing ⚠️
internal/assistant/openai_chat.go 40.00% 3 Missing ⚠️
internal/terminal/app.go 88.88% 3 Missing ⚠️
... and 7 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #82      +/-   ##
==========================================
+ Coverage   66.76%   67.44%   +0.68%     
==========================================
  Files         207      211       +4     
  Lines       17869    18531     +662     
==========================================
+ Hits        11930    12498     +568     
- Misses       4841     4888      +47     
- Partials     1098     1145      +47     
Flag Coverage Δ
unittests 67.44% <79.87%> (+0.68%) ⬆️

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (4)
internal/terminal/prompt_queue.go (1)

27-27: ⚡ Quick win

Consider using busy() consistently across the codebase.

This code now uses app.busy() to guard queue processing, but internal/terminal/message_layout.go:159 explicitly checks app.working || app.compacting. For better maintainability, consider using the busy() abstraction consistently wherever both states need to be checked.

🤖 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/prompt_queue.go` at line 27, Replace direct checks of the
two state flags with the busy() abstraction: wherever you see code checking
app.working || app.compacting (e.g. in the message layout rendering logic that
references working and compacting), change it to call app.busy() instead so the
busy logic is centralized and consistent with the queue guard that already uses
app.busy(); verify busy() correctly returns (app.working || app.compacting)
before making the replacements.
internal/terminal/render_test.go (1)

301-314: ⚡ Quick win

Assert the rendered compacting colors, not just the palette selector.

This test currently proves the compacting text and workingShimmerPalette() output, but it would still pass if the render path kept applying the default shimmer palette. Please render the line into a frame and assert the spinner/content cell colors too.

Suggested test tightening
 func TestRenderCompactingIndicatorUsesCompactionTextAndPalette(t *testing.T) {
 	t.Parallel()

 	app := newRenderTestApp(t)
 	app.compacting = true
 	app.workFrame = 0
 	lines := app.renderWorkingIndicator(40)
+	require.Len(t, lines, 3)
 	if !strings.HasPrefix(lines[1].Text, "⠋ Compacting context...") {
 		t.Fatalf("compacting indicator text = %q", lines[1].Text)
 	}
-	if got, want := app.workingShimmerPalette().bright, compactingShimmerBrightColor(); got != want {
+	palette := app.workingShimmerPalette()
+	if got, want := palette.bright, compactingShimmerBrightColor(); got != want {
 		t.Fatalf("compacting shimmer bright = %v, want %v", got, want)
 	}
+	app.frame = newCellBuffer(40, len(lines), tcell.StyleDefault)
+	app.writeStyledLine(1, 40, lines[1])
+	if got, want := app.frame.cell(0, 1).Style.GetForeground(), palette.bright; got != want {
+		t.Fatalf("spinner foreground = %v, want %v", got, want)
+	}
+	if got, want := app.frame.cell(2, 1).Style.GetForeground(), workingShimmerColor(0, 0, len("Compacting context..."), palette); got != want {
+		t.Fatalf("label foreground = %v, want %v", got, want)
+	}
 }

As per coding guidelines, **/*_test.go: Prefer table-driven tests for core behavior and regression tests for terminal rendering bugs.

🤖 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/render_test.go` around lines 301 - 314, The test
TestRenderCompactingIndicatorUsesCompactionTextAndPalette currently only checks
the text and the palette selector; update it to render the produced line into a
frame and assert the actual cell colors used by the spinner/content match the
compacting palette values: use newRenderTestApp(...).renderWorkingIndicator(40)
to get the lines, then render that line into a frame (via the same rendering
helper used elsewhere in tests) and assert the spinner and content cell
foreground (and background if applicable) equal compactingShimmerBrightColor()
(and any other compacting shimmer values) rather than just comparing
workingShimmerPalette(); keep the existing checks for text but add explicit
assertions on the rendered cell Color/Attribute values to ensure the renderer
applied the compacting palette.
config.example.yaml (1)

48-54: ⚡ Quick win

Document context.keep_recent_tokens in the sample config.

setDefaults now exposes context.keep_recent_tokens, but the example config does not. That makes the retained-tail compaction budget effectively undiscoverable for users copying this file.

Proposed update
 context:
   preflight_enabled: true
   # 0 uses the selected model max_tokens, otherwise 16k capped at 20% of context.
   output_reserve_tokens: 0
   provider_reserve_tokens: 2048
   safety_margin_tokens: 8192
+  keep_recent_tokens: 20000
🤖 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 `@config.example.yaml` around lines 48 - 54, Add a documented entry for
context.keep_recent_tokens to the example config (under the existing context
block) so users can see and copy the retained-tail compaction budget; the key
name must match context.keep_recent_tokens and the value should reflect the
default used by setDefaults (or explicitly state the numeric default if known),
plus a brief comment explaining it controls the retained-tail compaction budget
(how many recent tokens to always keep) and how it interacts with
output_reserve_tokens/provider_reserve_tokens/safety_margin_tokens.
internal/database/session_store.go (1)

237-247: ⚡ Quick win

Wrap the new entry-data failures with repository context.

These new decode/encode paths return raw errors, so callers lose whether the failure came from persisting ModelFacing metadata or rebuilding thinking-level state. This package already uses oops, so these branches should preserve that context too.

Suggested fix
 	if options.modelFacing != nil {
 		data, err := dataFromEntry(&entry)
 		if err != nil {
-			return nil, err
+			return nil, oops.In("database").Code("decode_entry_data").Wrapf(err, "decode entry data")
 		}
 		data.ModelFacing = options.modelFacing
 		dataJSON, err := dataJSONFromEntity(&data)
 		if err != nil {
-			return nil, err
+			return nil, oops.In("database").Code("encode_entry_data").Wrapf(err, "encode entry data")
 		}
 		entry.DataJSON = normalizeDataJSON(dataJSON)
 	}
 func applyThinkingLevelContext(contextEntity *SessionContextEntity, entry *EntryEntity) error {
 	data, err := dataFromEntry(entry)
 	if err != nil {
-		return err
+		return oops.In("database").Code("decode_entry_data").Wrapf(err, "decode thinking level entry data")
 	}
 	contextEntity.ThinkingLevel = data.ThinkingLevel
 
 	return nil
 }

Based on learnings: Applies to **/*.go : Use oops.In("domain").Code("code").Wrapf(err, "message") for contextual errors in packages that already use samber/oops

Also applies to: 619-621

🤖 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/database/session_store.go` around lines 237 - 247, The error returns
from dataFromEntry(&entry) and dataJSONFromEntity(&data) (and the similar spots
around lines ~619-621) should be wrapped with samber/oops context so callers
know the failure came from persisting ModelFacing vs rebuilding state; replace
plain returns like `return nil, err` with oops wraps such as `return nil,
oops.In("database").Code("decode_entry").Wrapf(err, "session_store: failed to
decode entry for ModelFacing")` for dataFromEntry errors and `return nil,
oops.In("database").Code("encode_entry").Wrapf(err, "session_store: failed to
encode entry after setting ModelFacing")` for dataJSONFromEntity errors
(consistent naming for code/domain is fine), and apply the same wrapping pattern
to the analogous returns at the other referenced location (lines ~619-621) so
all decode/encode failures include repository context.
🤖 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/assistant/context_budget.go`:
- Around line 46-47: The code currently uses positiveOrDefault which treats 0 as
“unset” and re-applies defaults for ProviderReserve and SafetyMargin;
add/replace with a helper that honors explicit zero (e.g., nonNegativeOrDefault
or zeroAllowedOrDefault) that returns the policy value when it is >= 0 and only
returns the default when the policy value is negative/unset, then use that
helper for ProviderReserve and SafetyMargin (replace
positiveOrDefault(policy.ProviderReserveTokens, defaultContextProviderReserve)
and positiveOrDefault(policy.SafetyMarginTokens, defaultContextSafetyMargin) and
do the same replacement for the other occurrences around lines 135-140).

In `@internal/assistant/runtime_context.go`:
- Around line 36-39: modelFacingMessages is filtering out database.RoleCustom
(and other passthrough roles) because it calls isModelFacingRole which still
returns false for those roles; update the logic so model-facing
custom/passthrough roles are preserved: either modify isModelFacingRole to
return true for database.RoleCustom and the passthrough role constants or change
modelFacingMessages to treat database.RoleCustom (and the same passthrough roles
handled elsewhere) as model-facing (e.g., include a conditional that calls
modelFacingMessage(&message) when message.Role == database.RoleCustom). Ensure
the change is applied to all occurrences (the blocks around lines 36-39, 54-60,
66-74) so custom extension context is not dropped before request construction.

In `@internal/config/config.go`:
- Around line 67-74: The ContextConfig struct fields only have mapstructure tags
so JSON/YAML unmarshalling will use the Go field names; update ContextConfig by
adding json and yaml tags matching the existing snake_case mapstructure keys for
each field (e.g., add `json:"output_reserve_tokens"
yaml:"output_reserve_tokens"` for OutputReserveTokens,
`json:"provider_reserve_tokens" yaml:"provider_reserve_tokens"` for
ProviderReserveTokens, and similarly for SafetyMarginTokens, KeepRecentTokens,
and PreflightEnabled) so direct JSON/YAML loads and round-trips use the same
keys as mapstructure.

In `@internal/terminal/prompt_submit.go`:
- Around line 29-33: The prompt text is being recorded into history before
checking app.compacting which causes duplicates if submission is blocked; to
fix, check app.compacting first and bail out before any history recording so the
composer restoration doesn't create a duplicate entry—move the history-recording
statement(s) (the code that saves the prompt text) so they execute only after
the compacting check passes and a submission will actually be sent, leaving the
existing app.setComposerText and app.setStatus logic unchanged when returning
false.

---

Nitpick comments:
In `@config.example.yaml`:
- Around line 48-54: Add a documented entry for context.keep_recent_tokens to
the example config (under the existing context block) so users can see and copy
the retained-tail compaction budget; the key name must match
context.keep_recent_tokens and the value should reflect the default used by
setDefaults (or explicitly state the numeric default if known), plus a brief
comment explaining it controls the retained-tail compaction budget (how many
recent tokens to always keep) and how it interacts with
output_reserve_tokens/provider_reserve_tokens/safety_margin_tokens.

In `@internal/database/session_store.go`:
- Around line 237-247: The error returns from dataFromEntry(&entry) and
dataJSONFromEntity(&data) (and the similar spots around lines ~619-621) should
be wrapped with samber/oops context so callers know the failure came from
persisting ModelFacing vs rebuilding state; replace plain returns like `return
nil, err` with oops wraps such as `return nil,
oops.In("database").Code("decode_entry").Wrapf(err, "session_store: failed to
decode entry for ModelFacing")` for dataFromEntry errors and `return nil,
oops.In("database").Code("encode_entry").Wrapf(err, "session_store: failed to
encode entry after setting ModelFacing")` for dataJSONFromEntity errors
(consistent naming for code/domain is fine), and apply the same wrapping pattern
to the analogous returns at the other referenced location (lines ~619-621) so
all decode/encode failures include repository context.

In `@internal/terminal/prompt_queue.go`:
- Line 27: Replace direct checks of the two state flags with the busy()
abstraction: wherever you see code checking app.working || app.compacting (e.g.
in the message layout rendering logic that references working and compacting),
change it to call app.busy() instead so the busy logic is centralized and
consistent with the queue guard that already uses app.busy(); verify busy()
correctly returns (app.working || app.compacting) before making the
replacements.

In `@internal/terminal/render_test.go`:
- Around line 301-314: The test
TestRenderCompactingIndicatorUsesCompactionTextAndPalette currently only checks
the text and the palette selector; update it to render the produced line into a
frame and assert the actual cell colors used by the spinner/content match the
compacting palette values: use newRenderTestApp(...).renderWorkingIndicator(40)
to get the lines, then render that line into a frame (via the same rendering
helper used elsewhere in tests) and assert the spinner and content cell
foreground (and background if applicable) equal compactingShimmerBrightColor()
(and any other compacting shimmer values) rather than just comparing
workingShimmerPalette(); keep the existing checks for text but add explicit
assertions on the rendered cell Color/Attribute values to ensure the renderer
applied the compacting palette.
🪄 Autofix (Beta)

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

Run ID: bede7f7e-6bc3-4e85-aec7-e58374542af3

📥 Commits

Reviewing files that changed from the base of the PR and between 63726a1 and e769a9e.

📒 Files selected for processing (47)
  • config.example.yaml
  • docs/context-management.md
  • internal/assistant/anthropic.go
  • internal/assistant/anthropic_internal_test.go
  • internal/assistant/client.go
  • internal/assistant/context_budget.go
  • internal/assistant/context_budget_test.go
  • internal/assistant/context_build.go
  • internal/assistant/context_compaction.go
  • internal/assistant/context_compaction_test.go
  • internal/assistant/messages.go
  • internal/assistant/openai_chat.go
  • internal/assistant/provider_hooks_internal_test.go
  • internal/assistant/provider_payload_hook_internal_test.go
  • internal/assistant/retry.go
  • internal/assistant/runtime.go
  • internal/assistant/runtime_context.go
  • internal/assistant/runtime_entries.go
  • internal/assistant/runtime_model.go
  • internal/assistant/runtime_slash.go
  • internal/assistant/runtime_test.go
  • internal/assistant/tool_schema.go
  • internal/config/config.go
  • internal/config/loader.go
  • internal/config/loader_test.go
  • internal/database/entry_metadata.go
  • internal/database/session_repository_test.go
  • internal/database/session_store.go
  • internal/terminal/app.go
  • internal/terminal/async_events.go
  • internal/terminal/autocomplete.go
  • internal/terminal/commands.go
  • internal/terminal/compact_commands.go
  • internal/terminal/compact_commands_test.go
  • internal/terminal/extension_events.go
  • internal/terminal/input.go
  • internal/terminal/input_escape.go
  • internal/terminal/message_layout.go
  • internal/terminal/prompt_cancel.go
  • internal/terminal/prompt_queue.go
  • internal/terminal/prompt_send_test.go
  • internal/terminal/prompt_submit.go
  • internal/terminal/render.go
  • internal/terminal/render_lines.go
  • internal/terminal/render_parity_test.go
  • internal/terminal/render_test.go
  • internal/terminal/runtime_buffers.go

Comment thread internal/assistant/context_budget.go Outdated
Comment thread internal/assistant/context_compaction.go Outdated
Comment thread internal/assistant/context_compaction.go
Comment thread internal/assistant/runtime_context.go
Comment thread internal/config/config.go
Comment thread internal/terminal/prompt_submit.go Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/terminal/prompt_send_test.go (1)

139-163: ⚡ Quick win

Consider consolidating submit tests into a table-driven format.

The file now has four separate TestSubmit* functions (lines 72–163) that each exercise different code paths through submit(). Based on learnings, table-driven tests are preferred for core behavior. Consolidating these into a single table-driven test would improve maintainability and align with the guideline, though the current structure is consistent with the file's existing style.

Example structure for consolidated table-driven test
func TestSubmit(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name          string
		composerText  string
		setupApp      func(*App)
		wantConsumed  bool
		wantWorking   bool
		wantMode      appMode
		wantQueued    []string
		wantComposer  string
		wantHistory   int
		wantRequest   bool
	}{
		{
			name:         "ignores empty prompt",
			composerText: "   ",
			setupApp:     func(app *App) {},
			wantConsumed: false,
			wantWorking:  false,
			wantRequest:  false,
		},
		{
			name:         "slash command opens panel",
			composerText: "/model",
			setupApp:     func(app *App) {},
			wantConsumed: false,
			wantMode:     modePanel,
			wantRequest:  false,
		},
		{
			name:         "queues when working",
			composerText: "queued follow-up",
			setupApp:     func(app *App) { app.working = true },
			wantConsumed: false,
			wantQueued:   []string{"queued follow-up"},
			wantRequest:  false,
		},
		{
			name:         "restores prompt without history when compacting",
			composerText: "wait for compaction",
			setupApp:     func(app *App) { app.compacting = true },
			wantConsumed: false,
			wantComposer: "wait for compaction",
			wantHistory:  0,
			wantRequest:  false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			client := newTerminalPromptClient(newTerminalCompletionResult("ok"), nil)
			app := newPromptSendTestApp(t, client)
			tc.setupApp(app)
			app.setComposerText(tc.composerText)

			consumed, err := app.submit(context.Background())
			if err != nil {
				t.Fatalf("submit error: %v", err)
			}
			if consumed != tc.wantConsumed {
				t.Errorf("consumed = %v, want %v", consumed, tc.wantConsumed)
			}
			// ... additional assertions
		})
	}
}

Based on learnings: Prefer table-driven tests for core behavior.

🤖 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/prompt_send_test.go` around lines 139 - 163, Consolidate
the four TestSubmit* tests into a single table-driven TestSubmit that enumerates
cases (empty prompt, slash command, queuing when app.working, and compacting
restore) and for each case uses newTerminalPromptClient and
newPromptSendTestApp, calls app.setComposerText, applies any setup via a
setupApp(func(*App)) (e.g., set app.compacting or app.working or app.mode), then
call app.submit(ctx) and assert consumed, app.composerText(), app.promptHistory
length, queued prompts slice, and whether client.request is nil; run each case
with t.Run and t.Parallel and keep existing helpers (submit(),
newTerminalPromptClient, newPromptSendTestApp) and the compacting case that
checks app.compacting and that no request was sent.
internal/assistant/context_compaction_internal_test.go (1)

13-81: ⚡ Quick win

Prefer table-driven coverage for planCompaction core scenarios.

These two tests validate the same core planner surface with different fixtures; combining them into table-driven cases will reduce duplication and make boundary regressions easier to add.

Refactor sketch
-func TestPlanCompactionAfterPreviousCompactionUsesPreviousKeptBoundary(t *testing.T) { ... }
-func TestPlanCompactionCutsAtTurnBoundaryWhenPossible(t *testing.T) { ... }
+func TestPlanCompaction(t *testing.T) {
+	t.Parallel()
+	cases := []struct {
+		name             string
+		entries          []database.EntryEntity
+		keepRecentTokens int
+		assertPlan       func(t *testing.T, plan compactionPlan)
+	}{
+		// existing scenarios here
+	}
+	for _, tc := range cases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			plan, err := planCompaction(tc.entries, tc.keepRecentTokens)
+			require.NoError(t, err)
+			tc.assertPlan(t, plan)
+		})
+	}
+}

As per coding guidelines **/*_test.go: Prefer table-driven tests for core behavior and regression tests for terminal rendering bugs.

🤖 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/assistant/context_compaction_internal_test.go` around lines 13 - 81,
Combine the two nearly-identical tests into a table-driven test that iterates
cases for planCompaction using compact fixtures created by compactionTestEntry;
specifically, create a single TestPlanCompaction_TableDriven (or rename one of
the existing tests) that defines cases (e.g. "uses previous kept boundary" and
"cuts at turn boundary") each containing the input slice of
database.EntryEntity, the target token budget (int), and expected fields
(PreviousSummary, SummarizedEntryIDs, KeptEntryIDs, FirstKeptEntryID, Messages
contents/length). For each case call planCompaction, assert no error, then
compare the returned plan against the case expectations (use the same field
assertions currently in
TestPlanCompactionAfterPreviousCompactionUsesPreviousKeptBoundary and
TestPlanCompactionCutsAtTurnBoundaryWhenPossible) to preserve behavior while
removing duplication and making it easy to add more boundary/regression cases.
🤖 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/assistant/context_budget_test.go`:
- Around line 102-104: The test currently checks numeric values from
usage.Breakdown which can be nil or missing and still return 0, producing false
positives; update the assertions to first verify the map contains the keys
before asserting their values. Specifically, in the test that uses the variable
usage and its map field Breakdown, add presence checks (e.g.,
require.Contains/require.True with a map lookup ok) for "reserve_provider" and
"reserve_safety" on usage.Breakdown, then assert their values (assert.Equal) as
before; ensure you still require.NoError(t, err) first and keep the same key
names.

In `@internal/assistant/context_compaction.go`:
- Around line 507-513: The token estimation uses the wrong prompt: replace uses
of compactionSummaryPrompt inside compactionRequestUsage with the actual dynamic
compactionSystemPrompt(plan.PreviousSummary) so the Usage field accurately
estimates tokens for the current SystemPrompt; update the call site where Usage
is set (the struct initializing SystemPrompt:
compactionSystemPrompt(plan.PreviousSummary), ThinkingLevel, CWD, Auth,
Messages, Usage: compactionRequestUsage(selectedModel, plan.Messages), Model:
*selectedModel) to pass compactionSystemPrompt(plan.PreviousSummary) (and make
the same replacement at the other compactionRequestUsage occurrence handling
update compactions) so preflight estimation uses the real system prompt.

---

Nitpick comments:
In `@internal/assistant/context_compaction_internal_test.go`:
- Around line 13-81: Combine the two nearly-identical tests into a table-driven
test that iterates cases for planCompaction using compact fixtures created by
compactionTestEntry; specifically, create a single
TestPlanCompaction_TableDriven (or rename one of the existing tests) that
defines cases (e.g. "uses previous kept boundary" and "cuts at turn boundary")
each containing the input slice of database.EntryEntity, the target token budget
(int), and expected fields (PreviousSummary, SummarizedEntryIDs, KeptEntryIDs,
FirstKeptEntryID, Messages contents/length). For each case call planCompaction,
assert no error, then compare the returned plan against the case expectations
(use the same field assertions currently in
TestPlanCompactionAfterPreviousCompactionUsesPreviousKeptBoundary and
TestPlanCompactionCutsAtTurnBoundaryWhenPossible) to preserve behavior while
removing duplication and making it easy to add more boundary/regression cases.

In `@internal/terminal/prompt_send_test.go`:
- Around line 139-163: Consolidate the four TestSubmit* tests into a single
table-driven TestSubmit that enumerates cases (empty prompt, slash command,
queuing when app.working, and compacting restore) and for each case uses
newTerminalPromptClient and newPromptSendTestApp, calls app.setComposerText,
applies any setup via a setupApp(func(*App)) (e.g., set app.compacting or
app.working or app.mode), then call app.submit(ctx) and assert consumed,
app.composerText(), app.promptHistory length, queued prompts slice, and whether
client.request is nil; run each case with t.Run and t.Parallel and keep existing
helpers (submit(), newTerminalPromptClient, newPromptSendTestApp) and the
compacting case that checks app.compacting and that no request was sent.
🪄 Autofix (Beta)

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

Run ID: bc5b97bc-6e1b-4465-8cac-0d8031ed68e1

📥 Commits

Reviewing files that changed from the base of the PR and between e769a9e and 189c1ea.

📒 Files selected for processing (19)
  • config.example.yaml
  • internal/assistant/anthropic.go
  • internal/assistant/context_budget.go
  • internal/assistant/context_budget_test.go
  • internal/assistant/context_compaction.go
  • internal/assistant/context_compaction_internal_test.go
  • internal/assistant/context_compaction_test.go
  • internal/assistant/messages.go
  • internal/assistant/openai_chat.go
  • internal/assistant/runtime_context.go
  • internal/assistant/runtime_context_internal_test.go
  • internal/config/config.go
  • internal/database/session_store.go
  • internal/terminal/compact_commands.go
  • internal/terminal/compact_commands_test.go
  • internal/terminal/message_layout.go
  • internal/terminal/prompt_send_test.go
  • internal/terminal/prompt_submit.go
  • internal/terminal/render_test.go
✅ Files skipped from review due to trivial changes (1)
  • config.example.yaml
🚧 Files skipped from review as they are similar to previous changes (9)
  • internal/terminal/message_layout.go
  • internal/terminal/render_test.go
  • internal/terminal/prompt_submit.go
  • internal/terminal/compact_commands_test.go
  • internal/assistant/openai_chat.go
  • internal/config/config.go
  • internal/assistant/messages.go
  • internal/terminal/compact_commands.go
  • internal/database/session_store.go

Comment on lines +102 to +104
require.NoError(t, err)
assert.Equal(t, 0, usage.Breakdown["reserve_provider"])
assert.Equal(t, 0, usage.Breakdown["reserve_safety"])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prevent false-positive map assertions in this test.

Line 103 and Line 104 can pass even if usage.Breakdown is nil or keys are missing, because map lookups return 0 by default. Assert map/key presence first.

Proposed test hardening
 	require.NoError(t, err)
+	require.NotNil(t, usage.Breakdown)
+	require.Contains(t, usage.Breakdown, "reserve_provider")
+	require.Contains(t, usage.Breakdown, "reserve_safety")
 	assert.Equal(t, 0, usage.Breakdown["reserve_provider"])
 	assert.Equal(t, 0, usage.Breakdown["reserve_safety"])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require.NoError(t, err)
assert.Equal(t, 0, usage.Breakdown["reserve_provider"])
assert.Equal(t, 0, usage.Breakdown["reserve_safety"])
require.NoError(t, err)
require.NotNil(t, usage.Breakdown)
require.Contains(t, usage.Breakdown, "reserve_provider")
require.Contains(t, usage.Breakdown, "reserve_safety")
assert.Equal(t, 0, usage.Breakdown["reserve_provider"])
assert.Equal(t, 0, usage.Breakdown["reserve_safety"])
🤖 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/assistant/context_budget_test.go` around lines 102 - 104, The test
currently checks numeric values from usage.Breakdown which can be nil or missing
and still return 0, producing false positives; update the assertions to first
verify the map contains the keys before asserting their values. Specifically, in
the test that uses the variable usage and its map field Breakdown, add presence
checks (e.g., require.Contains/require.True with a map lookup ok) for
"reserve_provider" and "reserve_safety" on usage.Breakdown, then assert their
values (assert.Equal) as before; ensure you still require.NoError(t, err) first
and keep the same key names.

Comment on lines +507 to +513
SystemPrompt: compactionSystemPrompt(plan.PreviousSummary),
ThinkingLevel: thinkingOff,
CWD: cwd,
Auth: auth,
Messages: plan.Messages,
Usage: compactionRequestUsage(selectedModel, plan.Messages),
Model: *selectedModel,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the actual compaction system prompt for token usage estimation.

SystemPrompt is dynamic (compactionSystemPrompt(plan.PreviousSummary)), but compactionRequestUsage still estimates with compactionSummaryPrompt. For update compactions, this underestimates context/input tokens and can let an oversized summarize request slip past preflight.

💡 Proposed fix
 func (runtime *Runtime) summarizeCompaction(
 	ctx context.Context,
 	cwd string,
 	sessionID string,
 	selectedModel *model.Model,
 	auth model.RequestAuth,
 	plan *compactionPlan,
 ) (string, error) {
+	systemPrompt := compactionSystemPrompt(plan.PreviousSummary)
 	request := &CompletionRequest{
 		OnEvent:           nil,
 		OnProviderObserve: runtime.emitProviderRequest,
 		OnProviderRequest: runtime.dispatchProviderRequestHook,
@@
-		SystemPrompt:      compactionSystemPrompt(plan.PreviousSummary),
+		SystemPrompt:      systemPrompt,
 		ThinkingLevel:     thinkingOff,
 		CWD:               cwd,
 		Auth:              auth,
 		Messages:          plan.Messages,
-		Usage:             compactionRequestUsage(selectedModel, plan.Messages),
+		Usage:             compactionRequestUsage(selectedModel, systemPrompt, plan.Messages),
 		Model:             *selectedModel,
 		ProviderAttempt:   0,
 	}
@@
-func compactionRequestUsage(selectedModel *model.Model, messages []database.MessageEntity) model.TokenUsage {
+func compactionRequestUsage(
+	selectedModel *model.Model,
+	systemPrompt string,
+	messages []database.MessageEntity,
+) model.TokenUsage {
@@
-	inputTokens := estimateInputTokens(compactionSummaryPrompt, messages)
+	inputTokens := estimateInputTokens(systemPrompt, messages)
@@
 		Breakdown: map[string]int{
-			jsonSystemRole:          estimateTokens(compactionSummaryPrompt),
+			jsonSystemRole:          estimateTokens(systemPrompt),
 			contextBreakdownHistory: estimateMessageTokens(messages),
 		},

Also applies to: 536-547

🤖 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/assistant/context_compaction.go` around lines 507 - 513, The token
estimation uses the wrong prompt: replace uses of compactionSummaryPrompt inside
compactionRequestUsage with the actual dynamic
compactionSystemPrompt(plan.PreviousSummary) so the Usage field accurately
estimates tokens for the current SystemPrompt; update the call site where Usage
is set (the struct initializing SystemPrompt:
compactionSystemPrompt(plan.PreviousSummary), ThinkingLevel, CWD, Auth,
Messages, Usage: compactionRequestUsage(selectedModel, plan.Messages), Model:
*selectedModel) to pass compactionSystemPrompt(plan.PreviousSummary) (and make
the same replacement at the other compactionRequestUsage occurrence handling
update compactions) so preflight estimation uses the real system prompt.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 4, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 (2)
internal/terminal/compact_commands_test.go (1)

101-194: 🏗️ Heavy lift

Refactor compact command state-transition tests into table-driven cases.

These added tests cover core behavior and share substantial setup/state assertions. A table-driven harness would reduce duplication and make future event variants easier to add consistently.

Based on learnings Applies to **/*_test.go : Prefer table-driven tests for core behavior and regression tests for terminal rendering bugs.

🤖 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/compact_commands_test.go` around lines 101 - 194, The three
tests (TestHandleCompactErrorUpdatesState, TestRunCompactSessionPostsError,
TestCancelActiveOperationCancelsCompaction) duplicate setup/assert patterns for
compaction state transitions; refactor them into a single table-driven test that
iterates cases (e.g., "compact error updates state", "run compact posts provider
error", "cancel active operation cancels compaction") and for each case sets up
the app state (app.compacting, app.activeCompaction /
activeCompactionState{Cancel:...}, session/client where needed), invokes the
relevant method (app.handleCompactAsyncEvent, app.runCompactSession,
app.cancelActiveOperation), and asserts the shared outcomes (app.compacting
false, app.activeCompaction nil, transcript last message or statusMessage
contains expected text); reference the existing symbols handleCompactAsyncEvent,
runCompactSession, cancelActiveOperation, activeCompactionState, app.transcript,
and statusMessage to locate code paths and replace the three tests with table
entries that parameterize inputs, invocation, and expected post-conditions.
internal/assistant/context_compaction_internal_test.go (1)

84-144: 🏗️ Heavy lift

Convert these compaction planner scenarios to a single table-driven test.

These are core behavior permutations and currently repeat setup/assertion patterns. A table-driven form will make adding edge cases cheaper and keep assertions consistent.

Refactor sketch
+func TestPlanCompactionScenarios(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name    string
+		entries []database.EntryEntity
+		keep    int
+		assert  func(t *testing.T, plan compactionPlan, err error)
+	}{
+		// previous-kept-boundary
+		// fallback-default-keep-recent
+		// reject-latest-compaction
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			plan, err := planCompaction(tc.entries, tc.keep)
+			tc.assert(t, plan, err)
+		})
+	}
+}

As per coding guidelines **/*_test.go: Prefer table-driven tests for core behavior and regression tests for terminal rendering bugs.

🤖 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/assistant/context_compaction_internal_test.go` around lines 84 -
144, Combine the two planCompaction scenarios into one table-driven test (e.g.,
replace TestPlanCompactionFallsBackToDefaultKeepRecentTokens and
TestPlanCompactionRejectsLatestCompaction with TestPlanCompaction_TableDriven)
that defines cases with fields: name, entries (built via compactionTestEntry),
tokenBudget, wantErr (bool), wantSummarizedNonEmpty (bool), wantKeptNonEmpty
(bool), and expectedFirstKeptEntryID when relevant; iterate cases calling
planCompaction(entries, tokenBudget) and assert according to the case (check err
presence, plan.SummarizedEntryIDs, plan.KeptEntryIDs, and plan.FirstKeptEntryID
for the latest-summary case where you must set
latestSummary.CompactionFirstKeptEntryID). Leave compactionSystemPrompt test as
a separate, small test (it already asserts compactionSystemPrompt contains
"Update the existing compaction summary" and the previous summary text).
🤖 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/compact_commands_test.go`:
- Around line 156-159: The select waiting on client.ready in the test uses a
tight 1s timeout (case <-time.After(time.Second)) which causes flakiness on CI;
increase the async startup timeout to something more generous (e.g., 5s or 10s)
in that select so the case <-time.After(...) gives the provider more time before
calling t.Fatal("compaction should start provider request") — update the timeout
literal where the select checks client.ready to a larger duration.

---

Nitpick comments:
In `@internal/assistant/context_compaction_internal_test.go`:
- Around line 84-144: Combine the two planCompaction scenarios into one
table-driven test (e.g., replace
TestPlanCompactionFallsBackToDefaultKeepRecentTokens and
TestPlanCompactionRejectsLatestCompaction with TestPlanCompaction_TableDriven)
that defines cases with fields: name, entries (built via compactionTestEntry),
tokenBudget, wantErr (bool), wantSummarizedNonEmpty (bool), wantKeptNonEmpty
(bool), and expectedFirstKeptEntryID when relevant; iterate cases calling
planCompaction(entries, tokenBudget) and assert according to the case (check err
presence, plan.SummarizedEntryIDs, plan.KeptEntryIDs, and plan.FirstKeptEntryID
for the latest-summary case where you must set
latestSummary.CompactionFirstKeptEntryID). Leave compactionSystemPrompt test as
a separate, small test (it already asserts compactionSystemPrompt contains
"Update the existing compaction summary" and the previous summary text).

In `@internal/terminal/compact_commands_test.go`:
- Around line 101-194: The three tests (TestHandleCompactErrorUpdatesState,
TestRunCompactSessionPostsError, TestCancelActiveOperationCancelsCompaction)
duplicate setup/assert patterns for compaction state transitions; refactor them
into a single table-driven test that iterates cases (e.g., "compact error
updates state", "run compact posts provider error", "cancel active operation
cancels compaction") and for each case sets up the app state (app.compacting,
app.activeCompaction / activeCompactionState{Cancel:...}, session/client where
needed), invokes the relevant method (app.handleCompactAsyncEvent,
app.runCompactSession, app.cancelActiveOperation), and asserts the shared
outcomes (app.compacting false, app.activeCompaction nil, transcript last
message or statusMessage contains expected text); reference the existing symbols
handleCompactAsyncEvent, runCompactSession, cancelActiveOperation,
activeCompactionState, app.transcript, and statusMessage to locate code paths
and replace the three tests with table entries that parameterize inputs,
invocation, and expected post-conditions.
🪄 Autofix (Beta)

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

Run ID: 3403c9fa-b397-478c-a825-6925f0f79419

📥 Commits

Reviewing files that changed from the base of the PR and between a298e60 and dcad89a.

📒 Files selected for processing (5)
  • internal/assistant/context_budget_test.go
  • internal/assistant/context_compaction_internal_test.go
  • internal/assistant/context_compaction_test.go
  • internal/assistant/runtime_context_internal_test.go
  • internal/terminal/compact_commands_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/assistant/context_budget_test.go

Comment on lines +156 to +159
select {
case <-client.ready:
case <-time.After(time.Second):
t.Fatal("compaction should start provider request")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Increase async startup timeout to reduce test flakiness.

The 1s wait is tight for contended CI runners and can cause intermittent failures unrelated to correctness.

Low-risk stabilization diff
-	case <-time.After(time.Second):
+	case <-time.After(3 * time.Second):
 		t.Fatal("compaction should start provider request")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
select {
case <-client.ready:
case <-time.After(time.Second):
t.Fatal("compaction should start provider request")
select {
case <-client.ready:
case <-time.After(3 * time.Second):
t.Fatal("compaction should start provider request")
🤖 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/compact_commands_test.go` around lines 156 - 159, The
select waiting on client.ready in the test uses a tight 1s timeout (case
<-time.After(time.Second)) which causes flakiness on CI; increase the async
startup timeout to something more generous (e.g., 5s or 10s) in that select so
the case <-time.After(...) gives the provider more time before calling
t.Fatal("compaction should start provider request") — update the timeout literal
where the select checks client.ready to a larger duration.

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.

2 participants