Improve Single-Agent Import Confirmation UX

Claude Code·Opus 4.8·computermode·2mo ago·4hr 4min·3 Checkpoints·4 file changes·+166/-15·191.5K tokens

When there is only one agent to import, the prompt says "Space to select, enter to confirm. Select none to skip."

There is nothing to select or skip, so it's confusing. It should say "enter to confirm" and have an option to go skip or go back

2mo ago·5m

Done. Build passes, existing import-offer tests pass, and lint is clean.

Change

cmd/entire/cli/setup_import.go — promptImportSelection now branches on the count of eligible agents:

  • Multiple agents: unchanged multi-select ("Space to select, enter to confirm. Select none to skip.").
  • Single agent: a new promptImportConfirmSingle shows a plain Import/Skip confirmation instead:
    • Title: Import existing <agent> sessions into Entire? (optional)
    • Description: <N> session(s) from the last 30 days. Enter to confirm.
    • Affirmative Import / Negative Skip

Declining or aborting returns an empty slice, so the caller skips the import exactly as before. This removes the confusing "space to select / select none to skip" wording when there's nothing to select between, and gives an explicit Skip option (the huh Confirm's Negative also acts as the back/skip control).

Verification

  • go build ./cmd/entire/cli/ — OK
  • go test for TestMaybeOfferSessionImport*, TestPluralSessions, TestImporterForAgent* — pass
  • mise run fmt && mise run lint — 0 issues

Not covered

I didn't add a test that drives the real huh form for the single-agent path. The existing suite mocks the entire sessionImportPrompt seam and never renders a form (form rendering needs a TTY), so there's no established pattern to hook into without new infrastructure. The branching is UI-level; the non-interactive path (--yes/no TTY, which auto-imports and never prompts) remains covered by TestMaybeOfferSessionImport_NonInteractiveAutoImportsAll. Let me know if you'd like me to add form-driving test scaffolding.

Push up the changes

2mo ago·51.8s

Pushed. Commit 8142c5068 is now on origin/feat/offer-import-on-enable.

  • Committed only cmd/entire/cli/setup_import.go (the untracked scratch files were left alone).
  • Lint passed before push.

If this branch has an open PR, the change is now part of it. Want me to update the PR description to mention the single-agent Import/Skip behavior?

set up another import test folder in /Users/ninawork/entire/devenv (let's call it importtest2) and add the same agents to it as importtest. I want to rerun now and enable multiple agents

2mo ago·27m

All six agents are discoverable through default discovery (exactly what the enable offer uses) once the env overrides point at importtest2's subfolders. Setup is complete and verified.

What I did

  • Created /Users/ninawork/entire/devenv/importtest2 as a fresh git repo (one commit — needed for HEAD/shadow branches), not enabled yet, so the first entire enable fires the first-run import offer.
  • Copied the same six agent sample folders + README from importtest (no .entire/.claude/.git state).
  • Rewrote the baked-in .../importtest → .../importtest2 cwd/gitRoot in the samples so codex/copilot discovery attributes them to importtest2.
  • Built /tmp/entire-dev from your current branch (8142c5068+dirty) so you're testing the single-agent fix.
  • Verified all six agents are found by default discovery: codex 1, cursor 2, gemini 1, pi 1, factoryai-droid 1, copilot-cli 1.

Run it now

The offer only discovers sessions if these ENTIRE_TEST_* overrides are set — paste the whole block:

When enable asks which agents to set up, pick 2+ (e.g. codex + cursor) → you'll get the multi-select import offer ("Space to select…"). Pick just one → you'll get the new Import/Skip confirm.

Two caveats worth knowing:

  • The offer is gated to first-run enable (project_import_reenable_gap). You get one shot per fresh repo — to retry the offer, re-run my setup to recreate importtest2 clean.
  • I didn't run enable myself since it's interactive and it's your test to drive.

What do the checkpoints look like now? I ran the import command.

2mo ago·6m

Checkpoints in importtest2

9 checkpoints, all tagged [imported] (read-only — no summaries, no rewind), on main. They map to 5 imported sessions across 4 agents:

AgentSessionCheckpointsTurn timestamps
Copilot CLIcopilot-sess-001206-24 07:00, 07:03
Codexcodex-sess-001206-24 08:00, 08:02
Cursorcursor-sess-001207-06 17:00
Cursor (nested)nested-sess107-06 17:00
Factory Droiddroid-sess-001207-06 17:00

Each checkpoint carries the original prompt as its intent (e.g. "Set up a GitHub Actions CI workflow…" → "Fix the failing gofmt lint in CI"), and codex/copilot preserved per-turn token counts (95–620) while cursor didn't (its transcript has no token data — expected).

One thing worth confirming: gemini and pi produced no checkpoints, even though both hook dirs (.gemini, .pi) got set up during enable and both were discoverable in the earlier dry-run (gemini 1 turn, pi 2 turns). Most likely you left those two unchecked in the import multi-select — but if you meant to import all six, that's a gap to look at.

Want me to import gemini + pi directly (entire import gemini / pi with the env overrides) to confirm they land, or dig into why the offer skipped them?

no it's ok, gemini not getting imported could be because it's going through a new name / path now. what will the user experience be when they push up checkpoints?

Imported 2 turn(s) from 1 session(s) (0 already imported). Imported 2 turn(s) from 1 session(s) (0 already imported). Imported 3 turn(s) from 2 session(s) (0 already imported). Imported 2 turn(s) from 1 session(s) (0 already imported).

Ready.

also, how do I check these checkpoints? like run explain on them etc.

2mo ago·6m

Both answers below, verified against a real push (I wired importtest2 to a throwaway local bare remote at /tmp/importtest2-remote.git).

What the push looks like

There's no separate "push checkpoints" command — it piggybacks on git push via the pre-push hook. On push the user sees one extra stderr line before the normal git output:

(For a slow network it prints progress dots and then done.) Details:

  • Target: the git push remote (origin) by default; only a dedicated checkpoint_remote in settings redirects it elsewhere. importtest2 uses the v1-branch store, so it pushes the whole entire/checkpoints/v1 branch (repos on the newer git-refs store instead see Pushing N checkpoint ref(s)…).
  • Imported checkpoints push identically to normal ones — all 9 landed on the remote v1 branch, each with full.jsonl, transcript.jsonl, prompt.txt, metadata.json. The [imported] / read-only status is just metadata and is preserved.
  • Non-fatal: a checkpoint-push failure is logged and swallowed — it never blocks the user's git push.
  • OPF prompt: if the OpenAI Privacy Filter were enabled, a pre-push re-redaction prompt would run first. It isn't enabled here, so no prompt.
  • ⚠️ Caveat: the git hook calls entire from PATH, not /tmp/entire-dev. So this push used your installed entire, not the dev binary with your single-agent fix. (The push path is unaffected by that change, so the UX is the same — but know that hook-driven flows in importtest2 won't exercise your dev binary unless you put it on PATH.)

How to inspect the checkpoints

All read-only, no env overrides needed (they read from the v1 branch):

Use the 12-char IDs from checkpoint list (e.g. 89c0b6a7d556 = copilot "Set up a GitHub Actions CI workflow"). Example from earlier: explain 89c0b6a7d556 shows session copilot-sess-001, tokens 180, the intent, a "No summary. Imported history is read-only" note, and the transcript.

Want me to leave the /tmp/importtest2-remote.git remote in place (handy for re-testing pushes) or remove it and the origin config so importtest2 is back to no-remote?

is this expected? ➜ importtest2 git:(main) ✗ entire session list No sessions.

why no sessions but there are checkpoints?

2mo ago·2m

Yes, that's expected. The two commands read from different stores, and import only writes one of them:

  • entire session list enumerates live session state — strategy.ListSessionStates(), which reads per-session files under .git/entire-sessions/. Those are created by real agent runs going through the hook lifecycle (start → steps → stop). In importtest2 that directory doesn't even exist.
  • entire checkpoint list reads checkpoint metadata off the entire/checkpoints/v1 branch.

The importer (agentimport package) writes checkpoint records straight onto the v1 branch and deliberately creates no session state — I confirmed it never touches SessionState/.git/entire-sessions/. That's the same reason each checkpoint is tagged [imported] and is read-only: it's historical data reconstructed from another agent's transcript, not a session Entire is actively tracking.

So: 9 checkpoints on v1, 0 live sessions in session state → "No sessions." Working as intended.

If you want to see sessions in session list, you'd need an actual agent run in importtest2 (which creates session state via hooks) — not an import.

Add the policy gate that pfleidi mentioned in his review of the PR.

2mo ago·5m

Done. Added the checkpoint-policy gate pfleidi asked for.

What I changed

cmd/entire/cli/setup_import.go — runSelectedImports now calls ensureCheckpointPolicyAllowsCheckpointData(ctx, repo) right after opening the repo and before any checkpoint write, mirroring import_cmd.go's order (open repo → policy gate → redaction → run). The one difference from the standalone command: the enable-time offer is best-effort, so an unsupported/unreadable policy is logged + noted + skipped rather than returning an error — enable must never fail because of the import offer.

cmd/entire/cli/setup_import_test.go — TestRunSelectedImports_UnsatisfiablePolicySkips installs a future-format policy (branch-v99) on the repo's policy ref and asserts the import is skipped with the note. It uses a nil importer, so if the gate ever regressed and let the loop run, the test would panic instead of silently passing.

Verification

  • go test for the new test + all import-offer tests — pass
  • mise run fmt && mise run lint — 0 issues

Note

pfleidi left a second comment on the same lines: the offer auto-imports all eligible sessions in non-interactive contexts (--yes, no TTY, or run from a script/agent), which he found surprising. That's a separate behavioral question (not the policy gate), so I left it alone. Want me to address that too — e.g. skip auto-import when there's no interactive terminal, or gate it behind an explicit flag?

Ok I suppose we want to support that too..

2mo ago·6m

Pushed. Commit d90849ac5 is on origin/feat/offer-import-on-enable (PR #1595).

What went up

Two fixes from pfleidi's review, in setup_import.go + tests:

  1. Checkpoint-policy gate — runSelectedImports now calls ensureCheckpointPolicyAllowsCheckpointData before writing any checkpoint data, matching entire import. Best-effort: an unsupported/unreadable policy skips the import (logged + noted) instead of failing enable.
  2. No silent non-interactive import — auto-import of all eligible agents now happens only under explicit --yes. A non-interactive enable without --yes (script, pipe, or no-TTY agent) skips import and prints Found importable history for N agent(s). Run 'entire import <agent>' to import it.

Tests: added TestRunSelectedImports_UnsatisfiablePolicySkips and TestMaybeOfferSessionImport_NonInteractiveWithoutYesSkips; existing --yes auto-import test still passes. Full cmd/entire/cli unit suite green.

Verification notes

  • The commit contains only my two files — confirmed above.
  • mise run lint reports one issue, but it's not mine: an unrelated working-tree change to cmd/entire/cli/explain.go removed a //nolint:exhaustive // ClaudeErrorUnknown handled by default comment. I did not touch or commit explain.go, so it isn't part of the push and CI won't see it. The only reported lint issue is that line; my files are clean.

Heads-up: that uncommitted explain.go change is still sitting in your working tree and will fail lint locally until it's either reverted or the //nolint restored. Want me to restore that comment, or leave it for whoever's editing explain.go?

The tests are failing

2mo ago

The tests are failing CI on the PR

2mo ago·6m

Pushed 9c707bf3a.

Root cause

The failing job was test-integration (c) — two integration tests in enable_import_test.go encoded the old behavior (non-interactive enable auto-imports):

  • TestEnableOffersImport_FirstRunAutoImports and TestEnableOffersImport_NotOfferedOnReEnable both asserted "Imported 2 turn(s)" from a no-TTY enable.

My previous commit intentionally changed that — a non-interactive enable without --yes now skips import and prints the hint — so those assertions no longer held. (The test job failed only because it gates on test-integration (c).)

Fix

Updated the tests to match the intended behavior (verified locally — all 4 pass):

  • FirstRunAutoImportsWithYes — passes --yes to exercise auto-import (confirmed --yes routes through setupAgentHooksNonInteractive → maybeOfferSessionImport and doesn't trigger GitHub setup since the repo already exists).
  • New NonInteractiveWithoutYesHints — asserts a no-TTY enable without --yes does not import and points at entire import.
  • NotOfferedOnReEnable — --yes on both enables (first imports, re-enable is gated by first-run).
  • NoHistoryIsSilent — unchanged (still silent).

Lint is clean for my changes (verified with the unrelated explain.go change stashed). Commit contains only the test file.

CI is re-running now. Want me to watch it and confirm green, or is that enough?

Reminder unchanged from before: the uncommitted explain.go edit (removed //nolint:exhaustive) is still in your working tree and will fail local lint until reverted — not mine, not pushed.