gstack-jsonl-merge: non-UTF-8 redirected Python stdout fails on Unicode JSONL and emits CRLF on Windows
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 82/100
調査の方向性
bin/gstack-jsonl-mergeから始め、特にマージしたレコードをstdoutに書き込む95行目付近のPythonブロックを確認してください。提供された非ASCIIのJSONL入力を使ってWindowsで再現し、Unicode出力と改行の両方を対象とするリグレッションテストを追加してください。有効なUTF-8入力が正常にマージされ、すべてのプラットフォームで出力がUTF-8かつLF改行のままであれば完了です。
索引モデルが issue の本文から書いたものです。
説明
What happened
On Windows, bin/gstack-jsonl-merge (the jsonl-append merge driver) failed while git re-applied an autostash in ~/.gstack (git pull --ff-only --autostash). Git reported the file as conflicted (UU projects/<slug>/learnings.jsonl) with no conflict markers. Running the driver by hand on the three index stages printed:
Traceback (most recent call last):
File "<stdin>", line 44, in <module>
File "...\Lib\encodings\cp1250.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode characters in position 273-286: character maps to <undefined>
The driver reads every input with encoding='utf-8', but writes the result with print(line) to its stdout (bin/gstack-jsonl-merge:95), which is redirected to the temp file (python3 - ... > "$TMP", line 51). In Windows environments where redirected Python stdout uses a legacy code page (cp1250 in this reproduction, with no PYTHONUTF8 or PYTHONIOENCODING set), print() cannot encode a JSONL line with a character the code page lacks, such as Cyrillic text in a learning, and raises UnicodeEncodeError. The script then exits 1 (lines 98-101), and git treats the merge as a conflict. The driver's header says exit 1 makes "git treats as conflict and stops the merge" (line 25), so this is the documented failure path, but the reproducer's input is valid UTF-8 JSONL.
A second effect of the same print(): on Windows, text-mode stdout writes CRLF, so even a successful merge rewrites LF input as CRLF, a full-file line-ending diff. We observed this on our Windows clone: merges through this driver turned three LF files of the artifacts repo into CRLF files, and the sync pushed them. No records were lost or doubled (the driver reads its inputs in text mode, which folds \r\n to \n), but the files can flip between LF and CRLF when merges run in environments with different stdout newline behavior. We restored LF by hand.
Expected: the driver writes UTF-8 with LF line endings whatever the platform and code page, and the merge succeeds. Possible fixes, as suggestions: write UTF-8 bytes with an explicit b"\n" to sys.stdout.buffer, or call sys.stdout.reconfigure(encoding='utf-8', newline='\n') at the top of the Python block. The environment variable PYTHONIOENCODING=utf-8 alone fixes the encoding but still writes CRLF on Windows: setting it by hand for the git command made the same merge succeed here, with CRLF output. A regression test with a non-ASCII line would cover it.
The crash needs a non-UTF-8 encoding for redirected Python stdout; the CRLF output happens with any Windows Python. Both occur when a real three-way merge or a stash re-apply in the artifacts repo invokes the configured jsonl-append driver: the brain-sync retry merge (bin/gstack-brain-sync:1382, when local and remote have diverged), and a user-run git pull or --autostash. Skill start's git merge --ff-only (bin/gstack-skill-start:440) does not run the driver when it fast-forwards.
Steps to reproduce
Windows, Git Bash, a Python whose locale.getpreferredencoding() is not UTF-8 (here cp1250). Synthetic data.
GS=~/.claude/skills/gstack; W=$(mktemp -d); cd "$W"
printf '{"ts":"2026-01-01T00:00:00Z","note":"base"}\n' > base; cp base ours; cp base theirs
printf '{"ts":"2026-01-02T00:00:00Z","note":"ours"}\n' >> ours
printf '{"ts":"2026-01-03T00:00:00Z","note":"\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82"}\n' >> theirs
bash "$GS/bin/gstack-jsonl-merge" base ours theirs; echo "exit=$?"; wc -l < ours
PYTHONIOENCODING=utf-8 bash "$GS/bin/gstack-jsonl-merge" base ours theirs; echo "exit_utf8=$?"; wc -l < ours
Output (traceback trimmed to its last line):
UnicodeEncodeError: 'charmap' codec can't encode characters in position 37-42: character maps to <undefined>
exit=1
2
exit_utf8=0
3
Readiness report
gstack doctor 1.91.318.0
ok install root ~/.claude/skills/gstack: agy global current (1.91.318.0), codex global current (1.91.318.0), claude global current (1.91.318.0)
ok rescue refs none in ~/.claude/skills/gstack
ok state root ~/.gstack durable=yes (selected by default)
ok bun 1.4.4 (floor 1.3.3, tested 1.4.2)
ok hooks 9 hook(s) parse
ok windows bash C:/Program Files/Git/bin/bash.exe (chosen by git --exec-path)
ok codex codex-cli 0.163.0; self-locate ok
ok codex probe cached MODEL_OK, 1h ago (not re-run; --live probes now)
warn artifacts sync status error
fix: ~/.claude/skills/gstack/bin/gstack-brain-sync --status
ok browse bundle browse/dist built
ok binaries find-browse, design, pdf, gstack-global-discover launch (4 of 4)
not configured cso native helper not built; /cso reports not assessed
ok claude code 2.1.314 (Claude Code)
ok session journal largest of 58: 19.3 MiB
ok guard log 34 decisions (2 denied)
ok CLAUDE.md no gstack section lists skills
14 ok, 1 warn, 1 not configured, 0 fail
(The artifacts sync warning is the companion issue below, taken before it was repaired.)
Environment
- Host (Claude Code, Codex, Cursor, ...) and its version: Claude Code 2.1.314
- OS: Windows 11, Git for Windows 2.46.0, Python 3.12.6 (
sys.stdout.encodingfor a redirected stdout andlocale.getpreferredencoding()are bothcp1250). - gstack 1.91.318.0 (1bd2330);
bin/gstack-jsonl-mergehas the same blob at main 53522b1, where the lines above were checked. - Related: companion issue: #3658 (a brain-sync clone that is only behind origin is refused by the case-collision check).
- 主要言語
- TypeScript
- スター
- 138k
- フォーク
- 20.5k
- 平均マージ
- 9時間 4分
- マージ済み PR(30日)
- 364
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
garrytan/gstack のほかの issue
-
ubicloud: usage is an awk syntax error on macOS, which also disables the vCPU quota preflight対応中かも @garrytan が今日担当しました。 オープンS3
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
ubicloud: reap and up die with "invalid VM name '-'" on a project with no VMs対応中かも @garrytan が今日担当しました。 オープンS3
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
autoplan guard: init via `cd <outside dir> &&` permanently denies the session with code evidence (host appends "Shell cwd was reset" to the JSON result)対応中かも @garrytan が今日担当しました。 オープンS3
難易度 2/5 1〜3時間 初心者へのやさしさ 79/100
メンテナーはふだん 1 日以内に返信
-
lib/pricing.ts has no rows for claude-haiku-5-5, gemini-4-argon or TypeSafe Jev, so gstack-eval-plan refuses those arms (EVAL_PRICE_MISSING)対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンenhancement S3
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
garrytan/gstack#3640 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
S3
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
garrytan/gstack の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
Equal-Vote/bettervoting#1754 ·
メンテナーはふだん 2 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 82/100
Doist/todoist-sdk-typescript#740 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 62/100
privatenumber/cleye#47 ·
-
clawsweeper:fix-shape-clear clawsweeper:queueable-fix clawsweeper:source-repro impact:auth-provider issue-rating: 🦞 diamond lobster no-stale P1
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
steipete/oracle#614 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
MoonshotAI/kimi-code#4591 ·
メンテナーはふだん 1 日以内に返信