fix(pi-shell): preserve heredocs on no-thread targets

This commit is contained in:
oldschoola
2026-05-25 13:38:37 -07:00
committed by can1357
parent ae927b6f81
commit b48cf6d255
3 changed files with 33 additions and 23 deletions
+28 -18
View File
@@ -2167,10 +2167,12 @@ fn setup_process_substitution(
//
// We keep the `F_SETPIPE_SZ` fast path for Linux (avoids a thread spawn
// for the common in-process case) but fall through to a detached writer
// thread on every other platform, and on Linux when the kernel rejects
// the requested size (body > `pipe-max-size`). The thread owns the
// writer; it terminates naturally when the consumer drains the pipe or
// drops the reader (`BrokenPipe`), so no `JoinHandle` is retained.
// thread on every other platform with OS threads, and on Linux when the
// kernel rejects the requested size (body > `pipe-max-size`). The thread
// owns the writer; it terminates naturally when the consumer drains the
// pipe or drops the reader (`BrokenPipe`), so no `JoinHandle` is retained.
// Targets without OS thread support keep upstream's synchronous write path
// so heredocs continue to work there instead of failing at thread spawn.
fn setup_open_file_with_contents(contents: &str) -> Result<OpenFile, error::Error> {
let (reader, mut writer) = std::io::pipe()?;
let bytes = contents.as_bytes();
@@ -2191,20 +2193,28 @@ fn setup_open_file_with_contents(contents: &str) -> Result<OpenFile, error::Erro
return Ok(reader.into());
}
}
// Generic path: detached writer thread. Writing inline deadlocks
// once `bytes.len()` exceeds the OS pipe buffer (Windows ~4 KiB,
// macOS 16-64 KiB), neither of which has a `F_SETPIPE_SZ`
// equivalent.
let payload = bytes.to_vec();
std::thread::Builder::new()
.name("brush-heredoc-writer".into())
.spawn(move || {
// `BrokenPipe` is expected when the consumer drops the
// reader before the body is fully written; there is
// nothing useful to do with that error here.
let _ = writer.write_all(&payload);
})?;
#[cfg(target_family = "wasm")]
{
writer.write_all(bytes)?;
drop(writer);
return Ok(reader.into());
}
#[cfg(not(target_family = "wasm"))]
{
// Generic path: detached writer thread. Writing inline deadlocks
// once `bytes.len()` exceeds the OS pipe buffer (Windows ~4 KiB,
// macOS 16-64 KiB), neither of which has a `F_SETPIPE_SZ`
// equivalent.
let payload = bytes.to_vec();
std::thread::Builder::new()
.name("brush-heredoc-writer".into())
.spawn(move || {
// `BrokenPipe` is expected when the consumer drops the
// reader before the body is fully written; there is
// nothing useful to do with that error here.
let _ = writer.write_all(&payload);
})?;
}
Ok(reader.into())
}
+1 -2
View File
@@ -128,6 +128,7 @@
- Fixed built-in `explore` agent failing every invocation with `schema_violation: files.0.ref: must not be present` on releases prior to 15.3.2 by renaming the `files[].ref` property to `files[].path` in the agent's output schema; `ref` is a JTD-reserved keyword (RFC 8927) and collides with JSON Type Definition's schema-reference form, so the converter previously dropped it from the generated JSON Schema. Defense-in-depth alongside the 15.3.2 converter fix ([#1379](https://github.com/can1357/oh-my-pi/issues/1379)).
- Increased the `yield` tool's schema-validation retry budget from 1 to 3 so subagents whose first structured-output attempt mismatches the declared output schema get up to three retries before the parent's post-mortem `schema_violation` check hard-fails the task. The tool now also surfaces remaining retry attempts and an explicit "call yield again with the corrected shape" directive in each rejection message, giving the model the context it needs to converge — particularly helpful for models like GLM that tend to invent per-element field names instead of following the declared schema.
- Fixed CLI PDF file arguments being decoded as raw bytes for local vision models; `.pdf` and other supported document files now go through the same Markit conversion path as the `read` tool before entering the prompt ([#1401](https://github.com/can1357/oh-my-pi/issues/1401)).
- Fixed the `bash` tool hanging until the 305 s hard timeout when a command writes a file via heredoc on Windows (bodies > ~4 KiB) or macOS (bodies > 16-64 KiB). Root cause was in the embedded brush shell; see `@oh-my-pi/pi-natives` changelog for the underlying fix.
## [15.3.2] - 2026-05-25
### Added
@@ -141,8 +142,6 @@
- Fixed parsing of inline `|TEXT` payloads containing whitespace on `»` and `«` inserts, which previously failed with unrecognized-op errors
- Fixed anchored insert handling so an inline `|TEXT` body matching the anchor line is treated as anchor decoration and no longer inserted as a duplicate
- Fixed clipboard image paste (Ctrl+V) silently failing on WSL2 by routing image reads through a `powershell.exe` bridge when WSL interop is detected, since `arboard` returns `ContentNotAvailable` under WSLg ([#1280](https://github.com/can1357/oh-my-pi/issues/1280))
- Fixed the `bash` tool hanging until the 305 s hard timeout when a command writes a file via heredoc on Windows (bodies > ~4 KiB) or macOS (bodies > 16-64 KiB). Root cause was in the embedded brush shell; see `@oh-my-pi/pi-natives` changelog for the underlying fix.
## [15.3.0] - 2026-05-25
+4 -3
View File
@@ -2,14 +2,15 @@
## [Unreleased]
### Fixed
- Fixed bash heredocs (`<<`) and here-strings (`<<<`) deadlocking the shell on Windows past ~4 KiB and on macOS past 16-64 KiB. `brush_core::interp::setup_open_file_with_contents` wrote the entire body into an anonymous pipe synchronously before handing the reader to the next command; once the body exceeded the OS pipe buffer the writer blocked forever and the `bash` tool timed out at the hard 305 s ceiling without ever launching the consumer. The Linux fast path still uses `F_SETPIPE_SZ` to grow the pipe in-place; every other OS-threaded platform (and Linux bodies above `pipe-max-size`) now decouples the write onto a fire-and-forget thread that terminates naturally on drain or `BrokenPipe`; no-thread targets keep the upstream synchronous path so heredocs do not fail at thread spawn.
## [15.3.2] - 2026-05-25
### Fixed
- Fixed `matchesKey` claiming `ctrl+m`/`ctrl+j`/`ctrl+i`/`ctrl+h`/`ctrl+[` for the single bytes terminals emit for Enter/Tab/Backspace/Escape in legacy mode. Pressing Enter no longer triggers a `ctrl+m` binding; the named keys now own those bytes and the colliding `ctrl+<letter>` combinations only match when the terminal disambiguates via the Kitty keyboard protocol or `modifyOtherKeys`. The same gate now also applies to `ctrl+alt+<letter>` legacy `ESC + <ctrl-char>` sequences (e.g. `\x1b\r` is Alt+Enter, not Ctrl+Alt+M). ([#1354](https://github.com/can1357/oh-my-pi/issues/1354))
### Fixed
- Fixed bash heredocs (`<<`) and here-strings (`<<<`) deadlocking the shell on Windows past ~4 KiB and on macOS past 16-64 KiB. `brush_core::interp::setup_open_file_with_contents` wrote the entire body into an anonymous pipe synchronously before handing the reader to the next command; once the body exceeded the OS pipe buffer the writer blocked forever and the `bash` tool timed out at the hard 305 s ceiling without ever launching the consumer. The Linux fast path still uses `F_SETPIPE_SZ` to grow the pipe in-place; every other platform (and Linux bodies above `pipe-max-size`) now decouples the write onto a fire-and-forget thread that terminates naturally on drain or `BrokenPipe`.
## [15.0.2] - 2026-05-15