@@ -692,3 +692,47 @@ func TestConversationIDOnAllSpans(t *testing.T) {
692692 assert .True (t , found , "span %q should have gen_ai.conversation.id" , span .Name )
693693 }
694694}
695+
696+ func TestModelOnToolSpanFromTranscriptWhenSessionStartOmitsModel (t * testing.T ) {
697+ dataDir := t .TempDir ()
698+ t .Setenv ("CLAUDE_PLUGIN_DATA" , dataDir )
699+ srv , spans , _ := collectingServer (t )
700+ t .Setenv ("DASH0_OTLP_URL" , srv .URL )
701+
702+ // Create transcript with model info.
703+ transcriptPath := filepath .Join (dataDir , "transcript.jsonl" )
704+ writeTranscript (t , transcriptPath , []string {
705+ `{"type":"user","message":{"role":"user","content":[{"type":"text","text":"hello"}]}}` ,
706+ `{"type":"assistant","requestId":"req_001","message":{"role":"assistant","model":"claude-opus-4-7","content":[{"type":"text","text":"hi"}],"usage":{"input_tokens":10,"output_tokens":5}}}` ,
707+ })
708+
709+ // SessionStart WITHOUT model — simulates the real-world bug.
710+ feed (t , `{"hook_event_name":"SessionStart","session_id":"sess-no-model"}` )
711+ feed (t , `{"hook_event_name":"UserPromptSubmit","session_id":"sess-no-model","prompt":"hello"}` )
712+ feed (t , fmt .Sprintf (`{"hook_event_name":"PostToolUse","session_id":"sess-no-model","tool_name":"Bash","tool_use_id":"tu1","tool_response":"ok","transcript_path":"%s"}` , transcriptPath ))
713+ feed (t , fmt .Sprintf (`{"hook_event_name":"Stop","session_id":"sess-no-model","transcript_path":"%s"}` , transcriptPath ))
714+
715+ require .Len (t , * spans , 2 , "expected tool span + chat span" )
716+
717+ // Tool span should have model from transcript fallback.
718+ toolSpan := findSpan (* spans , "execute_tool" )
719+ require .NotNil (t , toolSpan , "tool span should exist" )
720+ assertStringAttr (t , toolSpan .Attributes , "gen_ai.request.model" , "claude-opus-4-7" )
721+
722+ // Chat span should also have model from transcript.
723+ chatSpan := findSpan (* spans , "chat" )
724+ require .NotNil (t , chatSpan , "chat span should exist" )
725+ assertStringAttr (t , chatSpan .Attributes , "gen_ai.request.model" , "claude-opus-4-7" )
726+ }
727+
728+ func assertStringAttr (t * testing.T , attrs []otlp.Attribute , key , want string ) {
729+ t .Helper ()
730+ for _ , a := range attrs {
731+ if a .Key == key {
732+ require .NotNil (t , a .Value .StringValue , "attribute %q should have string value" , key )
733+ assert .Equal (t , want , * a .Value .StringValue )
734+ return
735+ }
736+ }
737+ t .Errorf ("attribute %q not found" , key )
738+ }
0 commit comments