Skip to content

Commit 36d5a13

Browse files
authored
Merge pull request #1867 from entireio/fix/import-progress-ansi-gating
fix(import): gate import progress ANSI on ShouldStyle (review follow-up to #1848)
2 parents 354e028 + a6e96ab commit 36d5a13

3 files changed

Lines changed: 27 additions & 9 deletions

File tree

cmd/entire/cli/import_progress.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ import (
1414
// tracks "Importing <agentName> sessions... (session i/N · turn j/M)";
1515
// callers must call the returned stop exactly once when the run finishes —
1616
// on both the success and error paths — so no spinner frame is left
17-
// dangling to corrupt whatever prints next. Otherwise (non-TTY, piped, or
18-
// ACCESSIBLE mode) it prints one plain, ANSI-free line per session from
19-
// SessionStart, and stop is a no-op.
17+
// dangling to corrupt whatever prints next. Otherwise — non-TTY, piped,
18+
// ACCESSIBLE mode, or a terminal that can't render ANSI (NO_COLOR,
19+
// TERM=cygwin; see interactive.ShouldStyle) — it prints one plain, ANSI-free
20+
// line per session from SessionStart, and stop is a no-op. The ShouldStyle
21+
// gate matches startUpdatableSpinner's own gate, so the spinner branch here
22+
// is taken only when the animation it drives can actually be rendered.
2023
func newImportProgressReporter(w io.Writer, agentName string) (progress *agentimport.Progress, stop func(success bool)) {
21-
if !interactive.IsTerminalWriter(w) || IsAccessibleMode() {
24+
// ShouldStyle already returns false for a non-terminal writer, so it
25+
// subsumes the non-TTY/piped case as well as NO_COLOR and TERM=cygwin.
26+
if IsAccessibleMode() || !interactive.ShouldStyle(w) {
2227
return &agentimport.Progress{
2328
SessionStart: func(sessionIndex, sessionTotal int, _, _ string, turnCount int) {
2429
fmt.Fprintf(w, "Importing %s session %d/%d (%d %s)...\n",

cmd/entire/cli/progress.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,18 @@ func startSpinner(w io.Writer, msg string) func(success bool) {
3333

3434
// startUpdatableSpinner is startSpinner's variant for an operation whose
3535
// status text changes while it runs (e.g. "session 2/5 · turn 3/10"). update
36-
// replaces the message the next frame draws — or, on a non-terminal writer,
36+
// replaces the message the next frame draws — or, on the non-animated path,
3737
// the message stop's completion line uses. update is safe to call at any
3838
// point, including before the spinner's first frame draws and after stop
3939
// returns. stop behaves exactly like startSpinner's, rendering whichever
4040
// message update last set (or msg, if update was never called).
41+
//
42+
// The live animation is emitted only when w both is a terminal and can render
43+
// ANSI (interactive.ShouldStyle) — the frames use cursor-control escapes
44+
// (\r\033[K), which a legacy console that can't handle ANSI (e.g.
45+
// TERM=cygwin) renders as literal "←[K" garbage, and which NO_COLOR asks us
46+
// to suppress. When styling is off we fall back to the completion-line-only
47+
// path, so no escape byte is ever written to such a writer.
4148
func startUpdatableSpinner(w io.Writer, msg string) (update func(string), stop func(success bool)) {
4249
var mu sync.Mutex
4350
current := msg
@@ -52,7 +59,9 @@ func startUpdatableSpinner(w io.Writer, msg string) (update func(string), stop f
5259
return current
5360
}
5461

55-
if !interactive.IsTerminalWriter(w) {
62+
// ShouldStyle already returns false for a non-terminal writer, so this
63+
// single gate also covers the plain non-TTY case.
64+
if !interactive.ShouldStyle(w) {
5665
return setMsg, func(success bool) {
5766
if success {
5867
fmt.Fprintf(w, "✓ %s\n", getMsg())

cmd/entire/cli/setup_import_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,13 @@ func TestRunSelectedImports_NonTTYProgressLines(t *testing.T) {
380380
// idempotent pass over an already-imported corpus (every turn hits
381381
// agentimport's TurnSkipped path, not TurnWritten) still prints one plain
382382
// progress line per session and reports the correct "0 imported" summary —
383-
// the non-TTY side of the P2 Codex's pre-push review caught (the TTY side is
384-
// covered by agentimport's TestRun_ReimportFiresTurnSkippedNotTurnWritten
385-
// and this package's TestNewImportProgressReporter_TTYAdvancesOnSkip).
383+
// the non-TTY side of the bug the P2 Codex's pre-push review caught. The TTY
384+
// side (a fully-skipped session must still sweep the spinner to turn M/M) is
385+
// covered at the agentimport layer by
386+
// TestRun_ReimportFiresTurnSkippedNotTurnWritten; the cli wiring it depends on
387+
// — newImportProgressReporter routing TurnSkipped and TurnWritten through one
388+
// shared advance path — has no direct test because the spinner branch only
389+
// runs against a real terminal.
386390
func TestRunSelectedImports_NonTTYProgressLines_Reimport(t *testing.T) {
387391
// Not parallel: chdirs into a temp repo and performs real checkpoint writes.
388392
dir := t.TempDir()

0 commit comments

Comments
 (0)