Skip to content

subprocess.Popen hang issues on "opencode run message --json format"? #11891

@davidbernat

Description

@davidbernat

Question

We are simply trying to launch opencode in the background via Python subprocess.Popen, and the execution hands indefinitely at the process.readline() moment. For clarity, this is the standard, very common, way to launch a subprocess (or opencode turn) into the command line from within Python, and yet there are a number of well-known peculiarities in Python between the process.readline() hanging indefinitely when new lines and empty byte strings are not encountered, potentially when the underlying command line process does something peculiar with its handling of newlines etc. I searched the opencode GitHub issues and found only one mention of subprocess.Popen (which really surprises me given how procedurally common this is a requirement to run from Python) so I figured it might be helpful for everyone others to debug this issue here.

This command works as intended when run directly from the command line in about 10 seconds, returning its first json formatted line (type=step_start) within about 7 seconds. All pretty common and in line with TUI expectations.

The use of --format json does not impact the situation, nor does simpler prompts, nor do prompts without files.

The replacement of cmd with trivial or simple cmds (e.g. `cmd=["echo", ""hello""] all work as expected with no issues.

I have searched my code repositories for how I have approached this the few times I remember this being an issue, and most of my implementations elsewhere are more or less identical, so I do not know what the issue is.

Do you?

cmd = ["opencode", "run", "\"Please complete the task using @test_TASK.md @test_PROJECT.md\"" "-f" "test_TASK.md" "-f" "test_PROJECT.md" "--format" "json"]

env = os.environ.copy()
proc = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while self.proc.poll() is None:
  # stdout, stderr = self.proc.communicate()  # tried this too
    for stream, is_error in [(self.proc.stdout, False), (self.proc.stderr, True)]:
        if stream and not stream.closed:
            try:
                line = stream.readline()  # <== HANGS HERE INDEFINITELY ON FIRST READ
                if line:
                    line = line.decode("utf-8", errors="replace").rstrip()
            except: pass
🔧 Executing command:
   opencode run "Please complete the task using @test_TASK.md @test_PROJECT.md" -f test_TASK.md -f test_PROJECT.md --format json
📝 Process 19836 started at 23:28:12
❤️ Heartbeat monitoring active (every 5 seconds)
️❤️ Heartbeat now=23:28:12    +0m 0s pid=19836 state=running_waiting
️❤️ Heartbeat now=23:28:17    +0m 5s pid=19836 state=running_waiting
️❤️ Heartbeat now=23:28:22    +0m10s pid=19836 state=running_waiting
️❤️ Heartbeat now=23:28:27    +0m15s pid=19836 state=running_waiting
️❤️ Heartbeat now=23:28:32    +0m20s pid=19836 state=running_waiting
️❤️ Heartbeat now=23:28:37    +0m25s pid=19836 state=running_waiting
️❤️ Heartbeat now=23:28:42    +0m30s pid=19836 state=running_waiting
️❤️ Heartbeat now=23:28:47    +0m35s pid=19836 state=running_waiting
️❤️ Heartbeat now=23:28:52    +0m40s pid=19836 state=running_waiting

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions