Skip to content

fix: stop task wait hanging on stdin and clearing the terminal line - #633

Draft
NickJosevski wants to merge 1 commit into
mainfrom
nj/fix-290
Draft

fix: stop task wait hanging on stdin and clearing the terminal line#633
NickJosevski wants to merge 1 commit into
mainfrom
nj/fix-290

Conversation

@NickJosevski

Copy link
Copy Markdown
Contributor

Fixes #526
Fixes #290

Two faults in task wait, both triggered by running it somewhere other than a plain interactive shell. They looked like separate reports; they are separate causes, in the same command.

#526 — hangs forever when shelled out to

task wait read stdin unconditionally to pick up piped task IDs:

taskIDs = append(taskIDs, util.ReadValuesFromPipe()...)

IsCalledFromPipe() is true whenever stdin is not a character device, and scanner.Scan() then blocks until the writer closes the pipe. A caller that shells out with stdin attached — Ruby's Open3.popen3, Python's subprocess.Popen — generally holds it open for the lifetime of the child, so the command never issued a single request, no matter that the task had long since finished.

This also explains the reporter's observation that runbook run worked fine from the same code: task wait is the only command in the CLI that calls ReadValuesFromPipe.

Fix: only consult stdin when no task IDs were given.

                        stdin held open by the caller
  before  octopus task wait ServerTasks-1 ...   STILL BLOCKED after 8s
  after   octopus task wait ServerTasks-1 ...   exited in 0s

Piping still works: echo ServerTasks-1 | octopus task wait behaves as before.

#290 — erases the terminal line when backgrounded

The spinner is installed in NewClientFactoryFromConfig:

if ask.IsInteractive() {
    httpClient = &http.Client{Transport: NewSpinnerRoundTripper()}
}

That runs from main.go:53, before cobra parses any flags. --no-prompt does not disable interactive mode until PersistentPreRunE (root.go:131), by which point the decision is already made — so --no-prompt never suppressed the spinner. Backgrounded, it redraws every poll by erasing the current terminal line, which is what made the terminal unusable.

Measured on a pty, counting ESC [ K (erase-line) sequences:

erase-line sequences
--no-prompt, before 8
--no-prompt, after 0
no flag (interactive), after 7 — spinner still works

Setting CI=1 already worked, because that calls DisableInteractive() at main.go:48, early enough to be seen. That difference is what pinned the cause.

Fix: the round-tripper decides per request rather than at construction, once the flag has been applied.

Verification

Against a live instance, with a FIFO held open by a background writer to imitate popen3, timing the CLI process itself rather than the shell pipeline — the first attempt measured the pipeline and looked like no change at all.

Unit tests cover the resolution of task IDs versus stdin, and the spinner's per-request decision including the case where interactivity changes after the round-tripper is built, which is the actual bug.

🤖 Generated with Claude Code

Two faults, both triggered by running `task wait` outside a plain
interactive shell.

`task wait` read stdin unconditionally to pick up piped task IDs. Reading
blocks until the writer closes the pipe, and a caller that shells out
with stdin attached, such as Ruby's Open3.popen3, usually holds it open
for the lifetime of the child. The command therefore never issued a
single request, no matter that the task had long finished. Only consult
stdin when no task IDs were supplied; piping them in still works.

The spinner was also installed based on interactivity read in
NewClientFactoryFromConfig, which runs from main before cobra parses
flags, so --no-prompt had not been applied yet and the spinner ran
regardless. Backgrounding the command then left it erasing the terminal
line every poll. The round-tripper now decides per request, once the
flag has been applied. Setting CI=1 already worked, since that disables
interactive mode early enough.

Fixes #526
Fixes #290

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

octopus task wait cli command does not return if shelled out to from ruby Moving an octocli command to the background clears the active line

1 participant