#21615: fix(tui): preserve main session model during heartbeat model override
size: S
experienced-contributor
Cluster:
Heartbeat Model Override Fixes
## Summary
- **Bug**: TUI status bar shows the heartbeat model instead of the main agent model after a heartbeat run
- **Root cause**: `persistSessionUsageUpdate()` in `session-usage.ts` unconditionally writes `modelUsed`/`providerUsed` to the session entry, so a heartbeat with a temporary model override overwrites the main session's model fields
- **Fix**: Skip persisting model/provider when the run is a heartbeat with a `heartbeatModelOverride`
Fixes #21524
## Problem
When a heartbeat is configured with a different model (e.g. `heartbeat.model: "openrouter/google/gemini-2.0-flash-001"`), the heartbeat runs in the main session and calls `persistRunSessionUsage()` with the heartbeat model. This overwrites `entry.model` and `entry.modelProvider` on the session entry. The TUI status bar reads these fields via `resolveSessionModelRef()` and displays the heartbeat model instead of the main agent's configured model.
The heartbeat runner already restores `updatedAt` after a heartbeat run (`restoreHeartbeatUpdatedAt`), but the model/provider fields were not similarly protected.
**Before fix:**
```
Main agent model: anthropic/claude-sonnet-4.6
Heartbeat model: openrouter/gemini-2.0-flash-001
TUI status bar shows: openrouter/gemini-2.0-flash-001 (wrong)
```
## Changes
- `src/auto-reply/reply/agent-runner.ts` — When `isHeartbeat && opts.heartbeatModelOverride`, pass `undefined` for `modelUsed`/`providerUsed` to `persistRunSessionUsage()` so the session entry retains the main agent's model
- `src/auto-reply/reply/session-usage.heartbeat-model-isolation.test.ts` — Regression test covering: heartbeat omits model (preserved), normal run updates model, heartbeat without override preserves model
**After fix:**
```
Main agent model: anthropic/claude-sonnet-4.6
Heartbeat model: openrouter/gemini-2.0-flash-001
TUI status bar shows: anthropic/claude-sonnet-4.6 (correct)
```
## Test plan
- [x] New test: 3 cases covering heartbeat model isolation
- [x] All 56 existing heartbeat tests pass
- [x] All 38 session-utils tests pass
- [x] Lint passes (0 warnings, 0 errors)
- [x] Format check passes on changed files
## Effect on User Experience
**Before:** After a heartbeat runs with a different model, the TUI status bar shows the heartbeat model (e.g. gemini-flash) instead of the main agent model (e.g. claude-sonnet). Users think their main session is misconfigured.
**After:** The TUI status bar always shows the main agent's model, regardless of which model the heartbeat used.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a bug where the TUI status bar incorrectly displayed the heartbeat model instead of the main session model after a heartbeat ran with a temporary model override.
**Changes:**
- Modified `agent-runner.ts` to conditionally pass `undefined` for `modelUsed`/`providerUsed` when `isHeartbeat && opts?.heartbeatModelOverride` is true, preventing the heartbeat model from overwriting the session entry
- Added comprehensive regression test covering three scenarios: heartbeat with override (preserves model), normal run (updates model), and heartbeat without override (preserves model)
- Updated CHANGELOG.md with user-facing fix description
The fix leverages existing fallback logic in `persistSessionUsageUpdate()` that uses `?? entry.model` and `?? entry.modelProvider`, ensuring the session retains its original model configuration when undefined values are passed.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The fix is narrowly scoped to a specific bug with clear root cause analysis. The implementation correctly uses existing fallback logic, adds comprehensive test coverage for all scenarios, and follows established patterns in the codebase (similar to `restoreHeartbeatUpdatedAt`). The change only affects heartbeat runs with model overrides and preserves all other behavior.
- No files require special attention
<sub>Last reviewed commit: 8fb533d</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#22277: fix: prevent heartbeat model override from bleeding into main session
by zhangjunmengyang · 2026-02-21
90.1%
#9429: fix: skip session model override for heartbeat runs
by dbottme · 2026-02-05
86.2%
#21791: feat(TUI): show main agent model in status footer
by chansuke · 2026-02-20
82.8%
#21932: fix(tui): eliminate stale model indicator lag in TUI
by graysurf · 2026-02-20
82.0%
#9721: fix: heartbeat model override not working for per-agent config (#9556)
by divol89 · 2026-02-05
81.8%
#6750: fix(tui): show session model overrides in status bar
by ewijaya · 2026-02-02
81.6%
#21847: fix(session): /new and /reset no longer carry over model overrides
by hydro13 · 2026-02-20
80.9%
#12786: fix: drop heartbeat runs that arrive while another run is active
by mcaxtr · 2026-02-09
80.8%
#19328: Fix: preserve modelOverride in agent handler (#5369)
by CodeReclaimers · 2026-02-17
80.7%
#17414: fix(sessions): refresh contextTokens when model override changes
by michaelbship · 2026-02-15
80.3%