Summary
#93 (fixed by #94) made a piped-stdin run produce clean, -q-style output. But it only fixed the output chrome — the input is still routed through the interactive REPL, consumed line-by-line, rather than read as a single single-shot message. The other half of #93's own suggested fix ("read stdin as the input, run one turn through the same scriptable path the MESSAGE arg uses") was never implemented.
Because piped stdin is still REPL keystrokes:
- A multi-line prompt is shredded into N independent turns — each line is sent to the agent as its own message and gets its own reply.
echo/cat file/heredoc/$( ) with any newline in it is silently split. Contrast -f/--file, which correctly sends a multi-line file as one message.
- Any prompt line beginning with
/ is intercepted as a slash command — not sent to the agent. A /quit line exits and silently drops the rest of the prompt; a /clear line wipes conversation context mid-prompt; other /... lines print Unknown command. Prompt content (a path, a code comment, a URL fragment, Markdown) can thus hijack the CLI.
For a CLI explicitly positioned as scriptable, cat prompt.txt | langstage-cli / printf '...\n...' | langstage-cli is the most idiomatic way to feed a prompt from a script, and it silently produces wrong results.
Environment
langstage-cli, version 0.6.30 (clean pip install langstage-cli from PyPI, fresh venv, no extras)
langstage-core 1.0.34, langgraph 1.2.11, Python 3.11.15, Linux
- Built-in
--demo agent (no key); reproduces with a user agent alike.
Minimal repro
$ langstage-cli --version
langstage-cli, version 0.6.30
# PROOF it's the REPL, not single-shot: a '/version' line RUNS THE COMMAND
# (a real single-shot message source would send "/version" to the agent).
$ printf '/version\n' | langstage-cli --demo
langstage-cli v0.6.30
Agent: Demo Agent
# A natural multi-line prompt -> 3 separate turns, one reply each:
$ printf 'Summarize:\nParis is the capital of France.\nIt has a population of 2M.\n' | langstage-cli --demo
(demo agent) You said: Summarize:
(demo agent) You said: Paris is the capital of France.
(demo agent) You said: It has a population of 2M.
# The SAME content via -f is ONE message (correct):
$ printf 'Summarize:\nParis is the capital of France.\nIt has a population of 2M.\n' > p.txt
$ langstage-cli --demo -f p.txt
(demo agent) You said: Summarize:
Paris is the capital of France.
It has a population of 2M.
# A '/quit' line inside a piped prompt EXITS and drops the rest:
$ printf 'analyze this\n/quit\nand then report\n' | langstage-cli --demo
(demo agent) You said: analyze this
# -> "and then report" is never sent; process exits.
# A '/clear' line wipes context mid-prompt:
$ printf 'remember X\n/clear\nwhat did I say\n' | langstage-cli --demo
(demo agent) You said: remember X
✓ Conversation cleared
(demo agent) You said: what did I say
Command-substitution capture is likewise corrupted — one intended prompt yields several concatenated replies:
$ ans=$(printf 'What is 2+2?\nAnd 3+3?\n' | langstage-cli --demo)
$ echo "[$ans]"
[(demo agent) You said: What is 2+2?
(demo agent) You said: And 3+3?]
Expected vs actual
Relationship to existing issues
Severity
major — silently corrupts multi-line piped prompts (the most idiomatic script form) into multiple independent turns, and lets prompt content (/… lines) hijack the CLI (drop/clear the run). Interactive TTY use is unaffected; single-line pipes happen to work.
Suggested fix
Implement #93's suggested behavior directly: when there is no MESSAGE/-f and not sys.stdin.isatty(), stdin.read() the whole stream and run it as one single-shot turn on the scriptable path (honoring -q/auto-quiet, which #94 already wired), instead of feeding lines to the REPL. Bonus, also still open from #93: accept the conventional -f - for stdin (today echo hi | langstage-cli --demo -f - errors Invalid value for '--file' / '-f': Path '-' does not exist; -f /dev/stdin is the only working, undocumented workaround).
Filed by the nightly power-user dogfooding routine (0.6.30). Also exercised and passing this run: clean pip install, README Quickstart (--demo, the stdlib StateGraph custom-agent example, -a/-g/-f, single-shot + --no-interactive, --verify pass/fail, init scaffold + run), Programmatic Use snippet, AG-UI serve entry point present, --help/--version/--show-config, langstage.toml + [configurable] thread_id (pinned thread persists across plain runs), --continue/--resume/--list-sessions, LANGSTAGE_* vs legacy DEEPAGENT_* (canonical wins, legacy warns on stderr) and legacy deepagents.toml notice, LANGSTAGE_CONFIG_HOME/LANGSTAGE_CLI_SESSIONS_DIR/LANGSTAGE_WORKSPACE_ROOT/LANGSTAGE_PERSIST, [session] persist=false, precedence (env > project toml > default), AIMessage with list content-blocks rendering, spec error paths (missing file / wrong attr / non-graph / uncompiled StateGraph — all clean), and a cp1252 pty run of --demo (stdout forced to UTF-8, no UnicodeEncodeError).
Summary
#93(fixed by#94) made a piped-stdin run produce clean,-q-style output. But it only fixed the output chrome — the input is still routed through the interactive REPL, consumed line-by-line, rather than read as a single single-shot message. The other half of #93's own suggested fix ("read stdin as the input, run one turn through the same scriptable path theMESSAGEarg uses") was never implemented.Because piped stdin is still REPL keystrokes:
echo/cat file/heredoc/$( )with any newline in it is silently split. Contrast-f/--file, which correctly sends a multi-line file as one message./is intercepted as a slash command — not sent to the agent. A/quitline exits and silently drops the rest of the prompt; a/clearline wipes conversation context mid-prompt; other/...lines printUnknown command. Prompt content (a path, a code comment, a URL fragment, Markdown) can thus hijack the CLI.For a CLI explicitly positioned as scriptable,
cat prompt.txt | langstage-cli/printf '...\n...' | langstage-cliis the most idiomatic way to feed a prompt from a script, and it silently produces wrong results.Environment
langstage-cli, version 0.6.30(cleanpip install langstage-clifrom PyPI, fresh venv, no extras)langstage-core 1.0.34,langgraph 1.2.11, Python 3.11.15, Linux--demoagent (no key); reproduces with a user agent alike.Minimal repro
Command-substitution capture is likewise corrupted — one intended prompt yields several concatenated replies:
Expected vs actual
-q/--quiet(and non-TTY auto-quiet) is ignored when the message comes from piped stdin — chrome +Nmstiming leak into stdout #93's suggested fix): with noMESSAGE/-fandnot sys.stdin.isatty(), read all of stdin as the input and run one single-shot turn through the same scriptable path theMESSAGEarg uses. A/versionline would be sent to the agent verbatim; a multi-line prompt would arrive as one message./are executed as CLI commands (/quittruncates,/clearwipes, others errorUnknown command).Relationship to existing issues
-q/--quiet(and non-TTY auto-quiet) is ignored when the message comes from piped stdin — chrome +Nmstiming leak into stdout #93 / fix: apply -q/non-TTY auto-quiet to a piped-stdin message (gh #93) #94 (closed): fix: apply -q/non-TTY auto-quiet to a piped-stdin message (gh #93) #94 applied the-q/non-TTY quiet gating to the piped path, so the output is clean and a single-line pipe looks fixed. This report is the input-semantics half that fix: apply -q/non-TTY auto-quiet to a piped-stdin message (gh #93) #94 did not address — stdin is still REPL-consumed, so multi-line and/-prefixed content break.bool("")is False):--no-interactiveis ignored and the CLI silently no-ops or hangs on the REPL instead of running a single-shot #123 (empty-stringMESSAGE).Severity
major — silently corrupts multi-line piped prompts (the most idiomatic script form) into multiple independent turns, and lets prompt content (
/…lines) hijack the CLI (drop/clear the run). Interactive TTY use is unaffected; single-line pipes happen to work.Suggested fix
Implement #93's suggested behavior directly: when there is no
MESSAGE/-fandnot sys.stdin.isatty(),stdin.read()the whole stream and run it as one single-shot turn on the scriptable path (honoring-q/auto-quiet, which #94 already wired), instead of feeding lines to the REPL. Bonus, also still open from #93: accept the conventional-f -for stdin (todayecho hi | langstage-cli --demo -f -errorsInvalid value for '--file' / '-f': Path '-' does not exist;-f /dev/stdinis the only working, undocumented workaround).Filed by the nightly power-user dogfooding routine (0.6.30). Also exercised and passing this run: clean
pip install, README Quickstart (--demo, the stdlibStateGraphcustom-agent example,-a/-g/-f, single-shot +--no-interactive,--verifypass/fail,initscaffold + run), Programmatic Use snippet, AG-UI serve entry point present,--help/--version/--show-config,langstage.toml+[configurable] thread_id(pinned thread persists across plain runs),--continue/--resume/--list-sessions,LANGSTAGE_*vs legacyDEEPAGENT_*(canonical wins, legacy warns on stderr) and legacydeepagents.tomlnotice,LANGSTAGE_CONFIG_HOME/LANGSTAGE_CLI_SESSIONS_DIR/LANGSTAGE_WORKSPACE_ROOT/LANGSTAGE_PERSIST,[session] persist=false, precedence (env > project toml > default), AIMessage with list content-blocks rendering, spec error paths (missing file / wrong attr / non-graph / uncompiled StateGraph — all clean), and a cp1252 pty run of--demo(stdout forced to UTF-8, noUnicodeEncodeError).