feat: track and display LLM model name in session info#581
Conversation
PR SummaryMedium Risk Overview Persists and updates model info in session state. Surfaces model in CLI output and plugin payloads. Written by Cursor Bugbot for commit ecf2d26. Configure here. |
There was a problem hiding this comment.
Pull request overview
Adds end-to-end tracking of the LLM model identifier through agent lifecycle hooks into persisted session state, and surfaces it in entire status so users can see which model is being used for an active session.
Changes:
- Extend lifecycle events and OpenCode/Cursor hook parsing to capture a
modelfield. - Persist
model_namein session state and update it across turns (including at turn end). - Display the model name in the active session listing and add/adjust relevant unit tests.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/strategy/phase_wiring_test.go | Updates InitializeSession calls for new parameter and adds tests asserting ModelName persistence/update semantics. |
| cmd/entire/cli/strategy/phase_prepare_commit_msg_test.go | Updates InitializeSession calls to match new signature. |
| cmd/entire/cli/strategy/manual_commit_test.go | Updates InitializeSession calls to match new signature in long-running strategy tests. |
| cmd/entire/cli/strategy/manual_commit_staging_test.go | Updates InitializeSession calls to match new signature in staging attribution tests. |
| cmd/entire/cli/strategy/manual_commit_session.go | Extends session initialization to accept a model string and store it in ModelName. |
| cmd/entire/cli/strategy/manual_commit_hooks.go | Extends InitializeSession to accept a model string and update state.ModelName when non-empty. |
| cmd/entire/cli/strategy/manual_commit_git.go | Updates internal fallback initialization calls for the new initializeSession signature. |
| cmd/entire/cli/status.go | Renders model name alongside agent label in the “Active Sessions” display when available. |
| cmd/entire/cli/session/state.go | Adds persisted ModelName field (model_name) to session state JSON. |
| cmd/entire/cli/lifecycle.go | Propagates event.Model into InitializeSession and into turn-end transition updates; logs model in lifecycle logs. |
| cmd/entire/cli/agent/event.go | Adds Model field to normalized agent lifecycle Event. |
| cmd/entire/cli/agent/opencode/types.go | Extends OpenCode turn-start/turn-end hook payload types to include model. |
| cmd/entire/cli/agent/opencode/lifecycle.go | Parses model from OpenCode turn-start and turn-end hook inputs into agent.Event.Model. |
| cmd/entire/cli/agent/opencode/lifecycle_test.go | Adds unit tests verifying model parsing on OpenCode turn-start (and empty-model behavior). |
| cmd/entire/cli/agent/opencode/entire_plugin.ts | Tracks the last assistant model and includes it in OpenCode turn-start/turn-end hook payloads. |
| cmd/entire/cli/agent/cursor/lifecycle.go | Includes parsed Cursor hook model field in the emitted lifecycle event. |
| cmd/entire/cli/agent/cursor/lifecycle_test.go | Adds unit tests verifying model parsing on Cursor turn-start (and empty-model behavior). |
Comments suppressed due to low confidence (6)
cmd/entire/cli/status.go:310
- The new status line includes the raw model name in parentheses. Model identifiers can be long and may push the line past the intended terminal width; consider truncating the displayed model (similar to how FirstPrompt is truncated) or otherwise constraining it for consistent status formatting.
// Line 1: Agent (model) · shortID
if st.ModelName != "" {
fmt.Fprintf(w, "%s %s %s %s\n",
sty.render(sty.agent, agentLabel),
sty.render(sty.dim, "("+st.ModelName+")"),
sty.render(sty.dim, "·"),
shortID)
cmd/entire/cli/status.go:316
- The status output now conditionally renders the model name, but there is no unit test asserting the new “Agent (model) · shortID” format. Since cmd/entire/cli/status_test.go already covers writeActiveSessions, add a test case with State.ModelName set to ensure this behavior is exercised.
// Line 1: Agent (model) · shortID
if st.ModelName != "" {
fmt.Fprintf(w, "%s %s %s %s\n",
sty.render(sty.agent, agentLabel),
sty.render(sty.dim, "("+st.ModelName+")"),
sty.render(sty.dim, "·"),
shortID)
} else {
fmt.Fprintf(w, "%s %s %s\n",
sty.render(sty.agent, agentLabel),
sty.render(sty.dim, "·"),
shortID)
}
cmd/entire/cli/strategy/manual_commit_session.go:195
- The doc comment for initializeSession lists agentType/transcriptPath/userPrompt but the function signature now also takes a model parameter. Update the comment to describe what the model argument represents and how it’s persisted (ModelName).
// A partial state may exist if the concurrent session warning was shown.
// agentType is the human-readable name of the agent (e.g., "Claude Code").
// transcriptPath is the path to the live transcript file (for mid-session commit detection).
// userPrompt is the user's prompt text (stored truncated as FirstPrompt for display).
func (s *ManualCommitStrategy) initializeSession(ctx context.Context, repo *git.Repository, sessionID string, agentType types.AgentType, transcriptPath string, userPrompt string, model string) (*SessionState, error) {
cmd/entire/cli/strategy/manual_commit_hooks.go:1609
- InitializeSession’s parameter docs don’t mention the new model argument. Please document what the model string represents (LLM model identifier) and the update behavior (only overwrites when non-empty).
// agentType is the human-readable name of the agent (e.g., "Claude Code").
// transcriptPath is the path to the live transcript file (for mid-session commit detection).
// userPrompt is the user's prompt text (stored truncated as FirstPrompt for display).
func (s *ManualCommitStrategy) InitializeSession(ctx context.Context, sessionID string, agentType types.AgentType, transcriptPath string, userPrompt string, model string) error {
cmd/entire/cli/agent/event.go:82
- The Event.Model field comment says it’s populated on TurnStart, but the Cursor/OpenCode implementations also populate Model on TurnEnd events. Update the comment to reflect that Model may be present on both TurnStart and TurnEnd when the agent provides it.
// Model is the LLM model identifier (e.g., "claude-sonnet-4-20250514").
// Populated on TurnStart events when the agent provides model info.
Model string
cmd/entire/cli/agent/opencode/types.go:21
- The comment on turnEndRaw says it “extends sessionInfoRaw”, but the struct doesn’t embed or reference sessionInfoRaw. Either embed sessionInfoRaw (anonymous field) or adjust the comment to avoid implying inheritance/embedding.
// turnEndRaw matches the JSON payload for turn-end (session idle).
// Extends sessionInfoRaw with model info captured during the turn.
type turnEndRaw struct {
SessionID string `json:"session_id"`
Model string `json:"model"`
}
|
bugbot run |
|
I think it's a good idea to keep the model name for claude and gemini as well - i.e. I'll see if it's easy to add on or will put in a follow up |
Validates that the Model field from the SessionStart hook is properly parsed and included in the normalized lifecycle Event. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Entire-Checkpoint: 008b9c5fcce9
- Add sessionStartRaw type with Model field for SessionStart hook - Parse Model from SessionStart event in parseSessionStart - Log model in handleLifecycleSessionStart - Extract persistEventMetadataToState helper to handle event->state metadata - Refactor transitionSessionTurnEnd to accept full event instead of just model - Persist model to session state on both session start and turn end This enables tracking which Claude model was used for each session, improving visibility into model selection and usage patterns. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Entire-Checkpoint: a83d9e34d525
I think we don't need to extract gemini, because the model name is included in the transcript. |
Same with claude code, has model in transcript on every assistant message. And looks like copilot has in transcript also. There isn't anywhere in UI where we see it, but we can consider if we'd want this anywhere. Unless we still want this additional visibility in json and |
No description provided.