fix(onboarding): a partial import-scan failure can no longer read as Done · Entire
fix(onboarding): a partial import-scan failure can no longer read as Done
a5b887d·
peyton-alt·15h ago·2 files·+70 added/-11 removed
When some (not all) agents' dry-run scans failed, their contribution was silently dropped: the History rung could claim 'N sessions imported' or 'no prior history found' while a failed agent's real status was unknown — and since each agent is the sole source for its own history, nothing else could correct it. Failed scans are now recorded (ScanFailed) and the rung degrades honestly: pending imports from healthy agents stay actionable (Missing), but a rung that would otherwise read Done or n/a renders Unknown naming the failed agent. Never memoized, as before.
Sessions
01KXR8Z26Z0YE2P4MH62ECXKKSView transcript
Changes
2
cmd/entire/cli
Monboarding_rungs.go+36/-11
Monboarding_rungs_test.go+34
58 unmodified lines
59
60
61
62
63
64
65
66
67
68
244 unmodified lines
313
314
315
312
313
316
317
318
319
320
321
322
323
324
325
152 unmodified lines
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
482
483
484
485
486
487
514
489
490
491
515
516
517
518
519
58 unmodified lines
Sessions int `json:"sessions"`
UnimportedTurns int `json:"unimported_turns"`
ImportedTurns int `json:"imported_turns"`
// ScanFailed marks an agent whose dry-run scan errored: its history
// exists (files were discovered) but its counts are unknown. Never
// memoized — failed scans force cacheable=false.
ScanFailed bool `json:"scan_failed,omitempty"`
}
// newOnboardingRungDeps wires the rung probes to their real backends: agent
244 unmodified lines
RepoRoot: repoRoot, Now: now, DryRun: true,
})
if runErr != nil {
// Best-effort per agent, but a partial result must not be
// memoized — it would stay wrong until the fingerprint moves.
// Best-effort per agent, but neither memoized (a partial result
// would stay wrong until the fingerprint moves) nor silently
// dropped: each agent is the sole source for its own history, so
// a failed scan is recorded as such and the rung degrades
// honestly instead of reporting Done/Missing with an undercount.
logging.Warn(ctx, "onboarding: import dry-run failed", "agent", d.imp.Name(), "error", runErr)
statuses = append(statuses, agentImportStatus{Agent: d.imp.Name(), ScanFailed: true})
runFailures++
cacheable = false
continue
}
totalSessions := 0
var unimported []agentImportStatus
var scanFailed []string
for _, s := range statuses {
if s.ScanFailed {
scanFailed = append(scanFailed, s.Agent)
continue
}
totalSessions += s.Sessions
if s.UnimportedTurns > 0 {
unimported = append(unimported, s)
}
}
if len(unimported) > 0 {
// Pending imports stay actionable even when another agent's
// scan failed — the offer is still worth running.
return onboarding.Check{
State: onboarding.StateMissing,
Detail: unimportedDetail(unimported),
Hint: importHint(unimported),
}
}
if len(scanFailed) > 0 {
// An agent with discovered history whose scan failed is the
// sole source for its own counts — claiming Done (or "no
// prior history") would overstate what is known.
return onboarding.Check{
State: onboarding.StateUnknown,
Detail: strings.Join(scanFailed, ", ") + " history scan failed",
Hint: "entire import",
}
}
if totalSessions == 0 {
return onboarding.Check{State: onboarding.StateNotApplicable, Detail: "no prior history found"}
}
if len(unimported) == 0 {
return onboarding.Check{
State: onboarding.StateDone,
Detail: fmt.Sprintf("%d sessions imported", totalSessions),
}
}
return onboarding.Check{
State: onboarding.StateMissing,
Detail: unimportedDetail(unimported),
Hint: importHint(unimported),
State: onboarding.StateDone,
Detail: fmt.Sprintf("%d sessions imported", totalSessions),
}
}
}
Mcmd/entire/cli/onboarding_rungs.go+36/-11
700 unmodified lines
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
700 unmodified lines
}
// A per-agent scan failure must not vanish: each agent is the sole source
// for its own history, so a rung that would otherwise read Done (or "no
// prior history found") degrades to Unknown naming the failed agent. Pending
// imports from healthy agents stay actionable regardless.
func TestImportRung_PartialScanFailureNeverClaimsDone(t *testing.T) {
t.Parallel()
check := func(statuses []agentImportStatus) onboarding.Check {
deps := onboardingRungDeps{
discoverImports: func(context.Context) ([]agentImportStatus, error) { return statuses, nil },
}
return importRung(deps).Check(context.Background())
}
imported := agentImportStatus{Agent: "claude-code", Sessions: 2, ImportedTurns: 2}
failed := agentImportStatus{Agent: "codex", ScanFailed: true}
pending := agentImportStatus{Agent: "cursor", Sessions: 1, UnimportedTurns: 3}
got := check([]agentImportStatus{imported, failed})
if got.State != onboarding.StateUnknown {
t.Errorf("imported+failed State = %v, want StateUnknown (not a false Done)", got.State)
}
if !strings.Contains(got.Detail, "codex") {
t.Errorf("Detail = %q, want the failed agent named", got.Detail)
}
if got := check([]agentImportStatus{failed, pending}); got.State != onboarding.StateMissing {
t.Errorf("failed+pending State = %v, want StateMissing (pending imports stay actionable)", got.State)
}
if got := check([]agentImportStatus{imported}); got.State != onboarding.StateDone {
t.Errorf("imported-only State = %v, want StateDone unchanged", got.State)
}
}
// One hint slot, several agents with pending history: naming only the first
// agent's subcommand would read as "import resolved" after running it. The
// hint points at the group command instead, which lists the per-agent