feat(attach): warn on empty transcript, add capture footer + amend-fail note · Entire
feat(attach): warn on empty transcript, add capture footer + amend-fail note
ae3a8aa→main·
computermode·2w ago·2 files·+209 added/-40 removed
Improve entire attach diagnosability and feedback:
- Warn (don't fail) when a transcript parses to no prompts and no model, so a contentless checkpoint is never produced silently. Stays a warning because agents whose user-content shape isn't matched (codex/copilot/pi/ factory) can legitimately yield empty metadata from a valid transcript; the warning goes to stderr and calls out --review when the empty prompt would also empty the review prompt.
- Print a "Captured:
· · " footer after a real attach, reusing the existing token-total/format helpers; each segment is omitted when absent. - Surface the amend-failure reason on stderr in addition to the existing debug log, so the user knows the best-effort amend was attempted before the manual-paste trailer fallback.
- Document the best-effort amend and transcript-based agent auto-detection in --force/Long help.
Threads an errW writer through runAttach for the stderr output, and extracts warnEmptyTranscriptMetadata / printAttachFooter / amendOrPrintTrailer helpers (the last de-duplicates the two amend call sites).
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Sessions
38c6ac0d707eView transcript
Changes
2
cmd/entire/cli
Mattach.go+68/-12
- Mattach_test.go+141/-28
95 unmodified lines
98
99
100
101
102
103
104
105
39 unmodified lines
145
146
147
145
148
149
150
151
21 unmodified lines
173
174
175
173
176
177
178
179
21 unmodified lines
201
202
203
201
204
205
206
207
43 unmodified lines
251
252
253
251
252
253
254
254
255
256
257
28 unmodified lines
286
287
288
289
290
291
292
86 unmodified lines
379
380
381
382
383
384
385
386
387
388
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
390
402
403
404
405
406
393
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
95 unmodified lines
attach a review without a declared skills list.
Works with any registered agent, including external agents enabled via
external_agents in settings. Run 'entire agent list' to see the full list.
// the user as clear stderr messages rather than generic cobra error output.
// The non-review path preserves the existing runAttach return-err behavior.
func runAttachSurfaceReviewErrors(cmd *cobra.Command, sessionID string, agentName types.AgentName, opts attachOptions) error {
err := runAttach(cmd.Context(), cmd.OutOrStdout(), sessionID, agentName, opts)
err := runAttach(cmd.Context(), cmd.OutOrStdout(), cmd.ErrOrStderr(), sessionID, agentName, opts)
if err != nil && opts.Review {
cmd.SilenceUsage = true
fmt.Fprintln(cmd.ErrOrStderr(), err.Error())
}
return []string{meta.FirstPrompt}
}
func runAttach(ctx context.Context, w io.Writer, sessionID string, agentName types.AgentName, opts attachOptions) error {
// Initialize structured logger so logging.Warn/Info write to .entire/logs/ not stderr.
if err := logging.Init(ctx, sessionID); err != nil {
// Init failed — logging will use stderr fallback, non-fatal.
}
}
Example Test Functions
// TestAttach_WarnsOnEmptyTranscriptMetadata: a transcript that parses to no
// user prompts and no model must still produce a checkpoint (warn, don't
// fail), with a warning written to stderr — never to stdout, where it would
// interleave with the success lines.
func TestAttach_WarnsOnEmptyTranscriptMetadata(t *testing.T) {
setupAttachTestRepo(t)
sessionID := "test-attach-empty-meta"
// Valid JSONL, but no user content and no model field: TurnCount and
// FirstPrompt both stay zero/empty.
setupClaudeTranscript(t, sessionID, `{"type":"assistant","message":{"role":"assistant","content":"hi"},"uuid":"a1"}`)
var out, errOut bytes.Buffer
if err := runAttach(context.Background(), &out, &errOut, sessionID, agent.AgentNameClaudeCode, attachOptions{Force: true}); err != nil {
t.Fatalf("runAttach should warn, not fail, on empty transcript metadata: %%v", err)
}
if !strings.Contains(errOut.String(), "no user prompts were parsed") {
t.Errorf("expected empty-transcript warning on stderr, got: %%q", errOut.String())
}
// The warning must not leak onto stdout.
if strings.Contains(out.String(), "no user prompts were parsed") {
t.Errorf("warning leaked onto stdout: %%q", out.String())
}
// The checkpoint must still be written.
store, err := session.NewStateStore(context.Background())
if err != nil {
t.Fatal(err)
}
state, err := store.Load(context.Background(), sessionID)
if err != nil {
t.Fatal(err)
}
if state == nil || state.LastCheckpointID.IsEmpty() {
t.Fatalf("expected checkpoint to be written despite empty metadata; state=%%+v", state)
}
}
// TestAttachSummaryLine covers the post-attach "Captured: …" footer builder:
// every field present, the token segment omitted when usage is nil, and the
// empty result when nothing is known.
func TestAttachSummaryLine(t *testing.T) {
t.Parallel()
tu := &agent.TokenUsage{InputTokens: 1000, OutputTokens: 300}
if got, want := attachSummaryLine(transcriptMetadata{TurnCount: 12, Model: "claude-opus-4-8"}, tu),
"12 turns · claude-opus-4-8 · 1.3k tokens"; got != want {
t.Errorf("attachSummaryLine() = %%q, want %%q", got, want)
}
}
// TestAttach_NonInteractivePrintsTrailerForManualPaste: with --force unset and
// no TTY (the test default), attach cannot prompt to amend, so it prints the
// Entire-Checkpoint trailer for manual paste instead of failing.
func TestAttach_NonInteractivePrintsTrailerForManualPaste(t *testing.T) {
setupAttachTestRepo(t)
sessionID := "test-attach-noninteractive"
setupClaudeTranscript(t, sessionID, `{"type":"user","message":{"role":"user","content":"hello"},"uuid":"u1"}
{"type":"assistant","message":{"role":"assistant","content":"hi"},"uuid":"a1"}`)
var out, errOut bytes.Buffer
// Force:false — exercise the non-interactive fallback branch.
if err := runAttach(context.Background(), &out, &errOut, sessionID, agent.AgentNameClaudeCode, attachOptions{}); err != nil {
t.Fatalf("runAttach failed: %%v", err)
}
re := regexp.MustCompile(`Entire-Checkpoint: ` + id.CheckpointPattern)
if !re.MatchString(out.String()) {
t.Errorf("expected Entire-Checkpoint trailer for manual paste, got:\n%%s", out.String())
}
}