AltimateAI / AltimateAI/altimate-code

altimate-code run wedges silently on inherited stdin when invoked as subprocess

未关闭
#934 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
bug
主要语言
TypeScript
星标
811
派生
134
平均合并
3 天 2 小时
30 天内合并 PR
50

描述

## Problem

When `altimate-code run "" --yolo` is invoked as a subprocess (Claude Code's Bash tool, Python `subprocess.run`, a CI runner, a plugin spawn with `stdio: "inherit"` on stdin), the process hangs at 0% CPU forever. No session is created, no log activity past `json migration complete`, no error — looks identical to a deadlock.

## Root cause

`packages/opencode/src/cli/cmd/run.ts:422` unconditionally reads stdin whenever the process is not attached to a TTY:

```ts
if (!process.stdin.isTTY) message += "\n" + (await Bun.stdin.text())
```

When the parent process inherits stdin from a higher-level non-pipe source and never closes its end, `Bun.stdin.text()` waits forever for an EOF that never arrives. The intent of the code was *"if someone pipes input in, append it to the message"* — but the guard `!process.stdin.isTTY` is too broad: it also matches *"stdin was inherited from a parent that isn't a TTY but also isn't closing"*.

## Reproducer

From a shell **with a positional message arg**:

```bash
# Simulate inherited-but-not-closed stdin (Bun.stdin.text() blocks forever)
exec 3< /dev/null # any open FD that isn't being written to
altimate-code run "say hi" --yolo <&3
# hangs at 0% CPU, no session created
```

Or, more realistically, from Python `subprocess.run([...], stdin=None)` inside another process — anywhere the child inherits a stdin file descriptor the parent doesn't explicitly close.

## Current mitigation (downstream)

The `altimate-opencode-plugin` works around this by spawning altimate-code with `stdio: ["ignore", "pipe", "pipe"]`, which closes the child's stdin from outside. See [altimate-opencode-plugin `plugins/altimate-code/index.ts:28`](https://github.com/AltimateAI/altimate-opencode-plugin/blob/main/plugins/altimate-code/index.ts#L28) and its comment at lines 177-182. Any caller that *doesn't* take that precaution still hits the wedge.

## Proposed fix

Only read stdin when no positional message has been provided:

```ts
if (!process.stdin.isTTY && message.trim().length === 0) {
message += "\n" + (await Bun.stdin.text())
}
```

This matches conventional CLI semantics (`echo "foo" | tool` reads stdin; `tool "explicit arg"` doesn't), and unblocks every subprocess caller that passes a positional message — which is the most common pattern in agent/skill/plugin invocations.

Pipe-only invocations without a positional arg (`echo "task" | altimate-code run --yolo`) continue to work unchanged.

## Test plan

- Unit test: with a positional message provided and `isTTY` false, `Bun.stdin.text()` is NOT invoked.
- Unit test: with no positional message and `isTTY` false, stdin IS read (existing pipe-only path).
- Smoke: `altimate-code run "say hi" --yolo <&-` returns in ~1s instead of hanging.

## Context

Surfaced during a multi-week experiment series running `altimate-code` as a Skill from Claude Code's `--print` subprocess flow. Originally diagnosed and patched in a local `altimate-code-preview` build (worktree at `altimate-code/.claude/worktrees/fix-run-stdin/`) — that patch has been belt-and-suspenders for the last several months. Re-verified 2026-06-12 against `main` (release `v0.8.7`, commit `146acea8e`): `run.ts:422` is unchanged; the line is still the original unconditional read.

Full writeup: `plugin-skill-experiments/03-issues-and-fixes.md` Issue #2.

PR incoming.

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。