Skip to content

Windows: UnicodeEncodeError crashes the process *after* the workflow succeeds, truncating the --silent JSON result #342

Description

@franklixuefei

Summary

On Windows with a non-UTF-8 locale, conductor run crashes with UnicodeEncodeError after the workflow has already completed successfully, while serializing its final JSON result to stdout. The workflow's real output is written correctly to disk, but:

  • the JSON result on stdout is truncated mid-field, so it cannot be parsed by any caller;
  • the process exits non-zero, so automation concludes the run failed;
  • with --web-bg, the dashboard shows "Connection lost — the Conductor process may have crashed", which reads as data loss.

The work is complete and safe on disk. Nothing in the UI or the exit status says so.

Environment

Conductor v0.1.26
Python 3.10.11
OS Windows 11, PowerShell 7
sys.stdout.encoding cp1252 (both redirected and at the console)
PYTHONUTF8 / PYTHONIOENCODING unset (default)

What I observed

A plan workflow ran ~55 minutes across 6 agent turns and finished. From the captured stderr, in this order:

✓ decision_summarizer  (171.97s, claude-sonnet-4.6, 91851 in/59 out, $0.3046, → ['file_path'])
   → $end
⏱ Total workflow execution: 3317.76s
Workflow completed successfully

Token Usage Summary
  Input:  1,050,049 tokens
  ...
  Total: $6.1694

Event log written to: ...events.jsonl

+--------------------------- ❌ UnicodeEncodeError ---------------------------+
|  'charmap' codec can't encode character '\u2192' in position 190:           |
|  character maps to <undefined>                                              |
+-----------------------------------------------------------------------------+

Workflow completed successfully is at stderr line 27412; UnicodeEncodeError at line 27432. The crash is strictly after successful completion.

The stdout JSON stops dead here:

{
  "purpose": "...",
  "plan_file": "...plan.md",
  "decisions_file": "...decisions.md",
  "technical_review_score": 92,
  "readability_review_score": 93,
  "technical_review_feedback":        <-- ends mid-field, file ends here

ConvertFrom-Json rejects it. Both output documents (88 KB and 13 KB) were written to disk correctly.

Root cause

U+2192 (→) is not encodable in cp1252. Python 3.10 on Windows uses the locale encoding for sys.stdout unless PYTHONUTF8/PYTHONIOENCODING is set — and it does so whether or not stdout is redirected (verified below, this is not a redirection-only issue as I first assumed).

The failing write is the final result JSON, because it embeds agent-generated content — here technical_review_feedback, which contains . Agent prose routinely contains → — ✅ ⚠️, so this is data-dependent: it fires whenever a reviewer/agent happens to use one, which makes it look intermittent.

Standalone reproduction (no workflow needed)

python -c "print('x'*185 + '\u2192')" > out.txt
# exit code: 1
# UnicodeEncodeError: 'charmap' codec can't encode character '\u2192'
#   in position 185: character maps to <undefined>
#   File "...\lib\encodings\cp1252.py", line 19, in encode

Identical codec, character, and error class as the conductor crash — only the offset differs, being a function of the string.

Encoding is cp1252 regardless of redirection

python -c "import sys; print(sys.stdout.encoding)"           # cp1252
python -c "import sys; ..." > file.txt                        # cp1252

So --web-bg does not cause it — it makes it maximally alarming, because the dashboard interprets the dead process as a crashed run.

Impact

  1. Looks like total data loss. ~55 min and $6.17 of agent work appeared lost. It wasn't — but recovering it meant knowing to go read %LOCALAPPDATA%\Temp\conductor\*.log and checking the output paths by hand.
  2. Breaks --silent as a machine interface. Its contract is "JSON result only"; a truncated document is worse than none, because naive callers may parse a prefix.
  3. Non-zero exit on a successful run — CI and wrapper scripts will report failure.
  4. Silently intermittent. Depends on whether an agent emitted a non-cp1252 glyph, so it will not reproduce reliably for anyone triaging it.

Suggested fix

Force UTF-8 on conductor's own streams at entry, independent of locale:

# conductor/cli entrypoint, before any output
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace")

errors="replace" is worth having as a belt-and-braces measure: conductor should never die while reporting a result it already computed. Serializing the final result with ensure_ascii=True would also make the JSON path safe by construction.

Given that the same crash would kill a run mid-flight if the offending glyph appeared in progress output, the entrypoint fix looks preferable to patching individual call sites.

Related

Secondary observation (not diagnosed)

With conductor --silent run ... --web-bg, the dashboard URL never reached my console — I found Dashboard: http://127.0.0.1:52913 only inside the background process's redirected stderr log, and initially had to recover it by scanning listening TCP ports for the conductor PID. I have not verified whether this is --silent suppressing it or --web-bg emitting it only to the child's stream, so I am reporting the observation rather than asserting a cause. It matters because tooling built on this workflow is instructed to surface that URL to the user.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions