can1357/oh-my-pi · error · io::Error (ErrorKind::InvalidInput)

Unterminated quote: {q}

Error message

Unterminated quote: {q}

What it means

pi-builtins' xargs implementation tokenizes its input (stdin or a file) with shell-like quoting: an opening `"` or `'` starts a quoted region whose matching close quote must appear before end-of-input. When the reader reaches EOF while still inside a quoted region, it raises this InvalidInput io::Error instead of silently producing a mangled argument, mirroring real xargs/shell behavior where an unclosed quote is a hard input error.

Source

Thrown at crates/pi-builtins/src/xargs.rs:538

		}

		let mut escape: Option<Escape> = None;
		let mut i = 0;
		loop {
			if i == pending.len() {
				pending.resize(4096, 0);
				// Already hit the end of our buffer, so read in some more data.
				let bytes_read = loop {
					match self.rd.read(host, &mut pending[..]) {
						Ok(bytes_read) => break bytes_read,
						Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
						Err(e) => return Err(e),
					}
				};

				if bytes_read == 0 {
					if let Some(Escape::Quote(q)) = &escape {
						return Err(io::Error::new(
							io::ErrorKind::InvalidInput,
							format!("Unterminated quote: {q}"),
						));
					}
					if i == 0 {
						return Ok(None);
					}
					pending.clear();
					break;
				}

				pending.resize(bytes_read, 0);
				i = 0;
			}

			match (&escape, pending[i]) {
				(Some(Escape::Quote(quote)), c) if c == *quote => escape = None,
				(Some(Escape::Quote(_)), c) => result.push(c),

View on GitHub (pinned to 9690622007)

Solutions

  1. Fix the input so every quote character is balanced — check the last line of the piped/file input for an unmatched " or ' before it reaches xargs.
  2. Escape embedded quote characters with a backslash (e.g. it\'s.txt) instead of relying on raw quotes in the input stream.
  3. Ensure the upstream producer finished writing and closed the stream correctly; a truncated pipe (SIGPIPE, crash) leaves the quote unterminated.
  4. Pre-process the input to strip or requote problematic filenames, e.g. with `printf '%q\n'` or a null-delimited producer (`find -print0 | xargs -0`) when supported.

Example fix

// before: unbalanced quote in piped input
echo "file1 'file2 | xargs pi-xargs
// error: Unterminated quote: '

// after: balanced quotes / escaped embedded quote
echo "file1 'file2'" | xargs pi-xargs
// or: echo "it\\'s.txt" | xargs pi-xargs
Defensive patterns

Strategy: validation

Validate before calling

function validateXargsInput(input) {
  let quote = null;
  for (const ch of input) {
    if (quote) { if (ch === quote) quote = null; }
    else if (ch === '"' || ch === "'") quote = ch;
    else if (ch === '\\') continue; // next char escaped
  }
  if (quote) throw new Error(`Input has unterminated ${quote} before xargs`);
}

Try / catch

try {
  await runXargs(input);
} catch (err) {
  if (err instanceof Error && /Unterminated quote/.test(err.message)) {
    console.error('Fix the unbalanced quote in the piped input and retry');
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Feeding xargs input via stdin or InputSource::Reader where a quote character (" or ') is opened and the input stream ends (bytes_read == 0) before the matching closing quote is read — e.g. a truncated pipe, a file cut off mid-line, or text pasted with a missing closing quote.

Common situations: Piping partially-written output from another process (producer killed mid-stream), copying a file list from an editor with an unbalanced quote, generated argument files with embedded apostrophes in filenames (e.g. "it's.txt") that weren't escaped, or a here-document/redirect that got truncated.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1350900ee5b59b18. Report an issue: GitHub.