Skip to content

fix(oneshot): write response to stdout instead of silently dropping it - #23007

Closed
luyao618 wants to merge 1 commit into
NousResearch:mainfrom
luyao618:fix/oneshot-empty-stdout
Closed

fix(oneshot): write response to stdout instead of silently dropping it#23007
luyao618 wants to merge 1 commit into
NousResearch:mainfrom
luyao618:fix/oneshot-empty-stdout

Conversation

@luyao618

Copy link
Copy Markdown
Contributor

Summary

Fixes #22975hermes -z (oneshot mode) returns empty stdout despite a successful API response.

Root Cause

run_oneshot() wraps the entire agent call tree in redirect_stdout(open(os.devnull, 'w')) to suppress diagnostic output. However, AIAgent.__init__ calls _install_safe_stdio(), which wraps sys.stdout in a _SafeWriter proxy. The proxy's __getattr__ forwards attribute lookups to the inner stream — when that stream is a devnull file handle, it lacks text-stream attributes like .encoding (returns None on some platforms) and .buffer (absent entirely). Downstream SDK code and logging formatters that inspect these attributes silently malfunction, causing agent.chat() to return an empty string.

Fix

  1. Replace open(os.devnull) with io.StringIO() as the redirect target. StringIO is a well-behaved in-memory text stream that exposes all standard file-object attributes, avoiding the edge cases that devnull triggers through _SafeWriter's proxy.

  2. Set agent._print_fn = lambda: None to route all agent-internal print calls (_safe_print, _vprint) through a no-op, eliminating a second vector for stdout interference during response capture.

Testing

  • All existing oneshot tests pass (11 passed, 7 skipped)
  • The fix is minimal (1 file, +22/-8 lines) and confined to hermes_cli/oneshot.py

Checklist

  • Scope: 1 file changed
  • Tests pass
  • SSH-signed commit

@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists labels May 10, 2026
@Bartok9

Bartok9 commented May 10, 2026

Copy link
Copy Markdown
Contributor

Reviewed this against the other two competing fixes for #22975 (#23033, and mine which I just closed).

This is the most complete fix. The StringIO over devnull change removes the underlying proxy edge case rather than papering over the symptom — luyao618 correctly identified that devnull's missing .encoding/.buffer attributes are what causes _install_safe_stdio() to malfunction downstream.

The _print_fn = lambda: None addition is a good belt-and-suspenders move that eliminates the second stdout interference vector.

Compared to my approach (_disable_streaming = True) which only fixed the streaming path, and KhanCold's fallback chain which adds complexity to work around the root cause rather than fix it — this one is the right call.

One minor suggestion: the test coverage in #23007 could be strengthened with a test that specifically verifies _run_agent returns non-empty when _disable_streaming is NOT set (to guard against regression back to the streaming path dropping content). But the fix itself is solid. 👍

@luyao618
luyao618 force-pushed the fix/oneshot-empty-stdout branch from 7e7c4b2 to 0ecf53b Compare May 17, 2026 06:47
@luyao618

Copy link
Copy Markdown
Contributor Author

Rebased onto current main — all blocking checks now green (lint, e2e, Windows footguns, nix, builds). The original Windows footgun + ty-diff failures were baseline drift, not regressions from this change.

Per @Bartok9's review above, this is the most complete of the three competing fixes for #22975: it uses io.StringIO() instead of open(os.devnull) so _SafeWriter proxy attribute lookups (e.g. .encoding, .buffer) keep working, and it neutralizes the agent's internal print path via _print_fn. Ready for a maintainer pass when there's time 🙏

Replace open(os.devnull) redirect target with io.StringIO() in
run_oneshot().  The devnull file handle lacks text-stream attributes
(.encoding, .buffer) that _SafeWriter and downstream SDK code rely on
via __getattr__ forwarding — causing agent.chat() to silently return
an empty string on some provider/platform combinations.

Additionally set agent._print_fn to a no-op lambda so _safe_print /
_vprint never interact with the redirected stream, eliminating a second
vector for stdout interference during response capture.

Closes NousResearch#22975
@luyao618

Copy link
Copy Markdown
Contributor Author

Closing — open too long, no longer relevant.

@luyao618 luyao618 closed this May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hermes -z (oneshot) returns empty stdout despite successful API response

3 participants