Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Interactive PTY: compound commands silently produce no output (WASI EFAULT), and shell diagnostics never reach the terminal

オープン
#1,893 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
52/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
静か
技術スタック
javascript, rust, shell, wasm

調査の方向性

まず crates/execution/assets/runners/wasi-module.js の _fdWrite から始め、特に _writeUint32 と synthetic-pipe のパスを確認してから、crates/CLAUDE.md にある WASM ホストプロセスブリッジのガイダンスを読んでください。AGENTOS_WASM_WASI_DEBUG=1 を有効にし、#1746 で追加された Brush のインタラクティブ PTY スイートを使用してください。複合コマンドの出力と brush の診断情報が EFAULT なしでターミナルに届けば完了です。

索引モデルが issue の本文から書いたものです。

説明

Summary

Under an interactive PTY (terminal.open() → brush), compound commands produce no output and no error. The same constructs work through exec() and through sh -c inside the same PTY, so this is specific to brush's interactive path, not to pipelines.

Two symptoms, likely one root cause — both are writes from the interactive shell that never reach the terminal:

  1. Compound commands (;, &&, |, ( ), backticks) print nothing. Redirecting stderr shows the real failure is WASI errno 21 (EFAULT) on a write.
  2. brush's own diagnostics never reach the PTY at all, so an unknown command silently returns to the prompt instead of printing command not found.

Symptom 2 is what makes symptom 1 invisible: the shell is reporting an error, it just goes nowhere.

Reproduction

import { AgentOs } from "@rivet-dev/agentos-core";
import common from "@agentos-software/common";

const vm = await AgentOs.create({ software: [common] });
const shell = await vm.terminal.open({ cols: 80, rows: 24 });
const id = shell.shellId;
let buf = "";
vm.onShellData(id, (e) => { buf += Buffer.from(e?.data ?? e).toString("utf8"); });
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
await sleep(1500);

for (const c of [
  "echo one",                       // OK
  "echo b; echo c",                 // FAIL - no output
  "echo d && echo e",               // FAIL
  "echo a | cat",                   // FAIL
  "(echo sub)",                     // FAIL
  "echo `echo backtick`",           // FAIL
  "echo 'semi;colon'",              // OK  - quoted, so it is one simple command
  "sh -c 'echo b; echo c'",         // OK  - same PTY, non-interactive brush
  "nosuchcmd",                      // FAIL - no "command not found"
]) {
  buf = "";
  await vm.terminal.write(id, c + "\n");
  await sleep(3000);
  console.log(JSON.stringify(c), "=>", JSON.stringify(buf.replace(/\r/g, "")));
}

Observed:

command interactive PTY exec()
echo one one ok
uname -a correct output ok
echo 'semi;colon' semi;colon ok
sh -c 'echo a | cat' a ok
sh -c 'echo b; echo c' b c ok
echo b; echo c nothing ok
echo d && echo e nothing ok
echo a | cat nothing ok
(echo sub) nothing ok
nosuchcmd nothing (no error) prints error

Note echo here is a brush builtin, so the failing case involves no external command at all.

The hidden error

Redirecting stderr to a file makes both failures visible:

$ nosuchcmd 2>/tmp/e1
$ cat /tmp/e1
error: command not found: nosuchcmd

$ echo b; echo c 2>/tmp/e2
$ cat /tmp/e2
error: echo: i/o error: Bad address (os error 21)

Under wasm32-wasip1, errno 21 is EFAULT (WASI numbering, not Linux's EISDIR), so "Bad address" and the code agree. brush parsed and executed the list correctly — the write to stdout failed.

Ruled out

  • Pipelines / ; themselves — exec("ls /opt/agentos/bin | head -5") returns correct output.
  • The kernel PTY, fd wiring, job control — sh -c 'echo a | cat' works inside the failing interactive shell.
  • stderr routing generally — a child's stderr does reach the terminal (sh -c 'echo to-stderr 1>&2' renders). Only the interactive shell's own stderr is lost.
  • TERM — identical failure with TERM=xterm-256color and TERM=dumb.
  • The terminal client — reproduced entirely server-side via vm.terminal.write(), no browser involved.

Where to look

_fdWrite in crates/execution/assets/runners/wasi-module.js:1404 returns EFAULT only via _writeUint32 (:413) — _mapFsError (:811) defaults to EIO, not EFAULT. So the failing path is the nwritten pointer write, or a synthetic-pipe branch, rather than a generic fs error.

crates/CLAUDE.md already documents this failure mode in the WASM host-process bridge:

child_process.poll returning ECHILD after an exit event's trailing-drain pass is terminal, not a new fault … post-exit drain loops must stop on ECHILD instead of converting a successful pipeline into WASI_ERRNO_FAULT.

Debug logging: AGENTOS_WASM_WASI_DEBUG=1 enables [agentos-wasi] traces from that runner.

Per software/CLAUDE.md ("fix portability one layer down, in the sysroot … patch the real upstream tool only as a fallback"), the fix likely belongs in the WASI/fd layer rather than in brush.

Related

  • #1881 — adjacent but different: kernel-stub command resolution inside subshells via exec, where echo hi | head -n 1 still works. Shares the "silent failure / misleading exit code" theme.
  • #1657 — other brush redirection divergences (>>, < file).
  • #1746 — merged; restored interactive shell correctness and added a real Brush interactive PTY suite, which is the natural harness for a regression test here.
主要言語
Rust
スター
4.8k
フォーク
272
平均マージ
13時間 24分
マージ済み PR(30日)
49

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

rivet-dev/agentos のほかの issue

rivet-dev/agentos の issue をすべて見る

似ている issue

Rust の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。