feat(import): warn when importing agent history while logged out · Entire

feat(import): warn when importing agent history while logged out

87b2fcc→main·

karthik-rameshkumar·yesterday·4 files·+90 added/-0 removed

Importing (via entire import or the entire enable offer) writes read-only
checkpoints to the local entire/checkpoints/v1 store and never syncs on its own
— sync happens later via the git pre-push hook, only once logged in. So an
import run while logged out succeeds locally but silently never reaches the
dashboard, and re-importing after login is a no-op (idempotent on local
existence).

Surface this: after a logged-out import that wrote local history, print a notice
that it's local-only and to log in before importing so it syncs. No-op when
logged in or when nothing was imported. Login detection is local-only (ENTIRE_TOKEN
or a current stored context) — no network on the import path. The existing
"Imported N turn(s)" line is unchanged.

This is fix #1 (the UX guard) for #1773; the sync-trigger for already-imported,
commit-less checkpoints is tracked separately there.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Changes

4

78 unmodified lines

79
80
81
82
83
84
85
86
87
88

78 unmodified lines

} fmt.Fprintf(c.OutOrStdout(), "%s %d turn(s) from %d session(s) (%d already imported). ", verb, res.TurnsImported, res.SessionsScanned, res.TurnsSkipped) // A dry run writes nothing locally, so there is nothing to sync. if !dryRun { warnIfImportNotSynced(c.OutOrStdout(), res.TurnsImported > 0 || res.TurnsSkipped > 0) } return nil }, }


Mcmd/entire/cli/import_cmd.go+4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

package cli

import ( "fmt" "io" "os"

"github.com/entireio/cli/cmd/entire/cli/auth" )

// importLoggedIn reports whether there is an active login the imported history // could eventually sync under: an ENTIRE_TOKEN env token, or a current stored // login context. It is local-only (reads env + contexts.json) and never makes a // network call, so it is safe on the import path. It is a package var so tests // can force either state. var importLoggedIn = func() bool { if os.Getenv(auth.EnvTokenVar) != "" { return true } ctxs, current, err := auth.Contexts() return err == nil && current != "" && len(ctxs) > 0 }

// warnIfImportNotSynced prints a one-time notice, when the user is not logged // in, that imported agent history is stored locally only and will not appear in // the Entire dashboard. It is a no-op when logged in or when nothing local was // imported. // // Import writes read-only checkpoints to the local entire/checkpoints/v1 store // and never syncs on its own; sync happens later via the git pre-push hook once // logged in. Importing while logged out therefore succeeds locally but silently // never reaches the dashboard — this notice surfaces that instead of leaving the // user to discover an empty dashboard (see issue #1773). func warnIfImportNotSynced(w io.Writer, importedLocalHistory bool) { if !importedLocalHistory || importLoggedIn() { return } fmt.Fprintln(w, "Note: you're not logged in, so this history was imported locally only and won't appear in your Entire dashboard.") fmt.Fprintln(w, "Log in with 'entire login' before importing to have your history synced.") }


Acmd/entire/cli/import_sync_notice.go+40

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

package cli

import ( "bytes" "strings" "testing" )

func TestWarnIfImportNotSynced(t *testing.T) { // Mutates the package-level importLoggedIn seam, so it cannot run in // parallel with other tests that read it. orig := importLoggedIn t.Cleanup(func() { importLoggedIn = orig })

cases := []struct { name string loggedIn bool imported bool wantNotice bool }{ {name: "logged out with imported history warns", loggedIn: false, imported: true, wantNotice: true}, {name: "logged in does not warn", loggedIn: true, imported: true, wantNotice: false}, {name: "nothing imported does not warn", loggedIn: false, imported: false, wantNotice: false}, {name: "logged in and nothing imported does not warn", loggedIn: true, imported: false, wantNotice: false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { importLoggedIn = func() bool { return tc.loggedIn } var buf bytes.Buffer warnIfImportNotSynced(&buf, tc.imported) got := buf.String() hasNotice := strings.Contains(got, "not logged in") && strings.Contains(got, "entire login") if hasNotice != tc.wantNotice { t.Fatalf("warnIfImportNotSynced(logged_in=%v, imported=%v): notice=%v, want %v; output=%q", ) } } } }


Acmd/entire/cli/import_sync_notice_test.go+39

220 unmodified lines

221 222 223 224 225 226 227 4 unmodified lines

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246

220 unmodified lines

// import_cmd.go; without it only always-on secret scanning would run. strategy.EnsureRedactionConfigured()

var importedLocalHistory bool for _, e := range selected { res, err := agentimport.Run(ctx, repo, e.imp, agentimport.Options{ RepoRoot: repoRoot, 4 unmodified lines

fmt.Fprintf(w, "Note: could not import %s history: %v\n", e.displayName, err) continue } if res.TurnsImported > 0 || res.TurnsSkipped > 0 { importedLocalHistory = true } fmt.Fprintf(w, "Imported %d turn(s) from %d session(s) (%d already imported).\n", res.TurnsImported, res.SessionsScanned, res.TurnsSkipped) } // Enable often runs before the user has logged in; surface once that a // logged-out import stays local and won't reach the dashboard (issue #1773). warnIfImportNotSynced(w, importedLocalHistory) }

// pluralSessions renders a session count with correct pluralization.