From 73062e24fe1941a08b5e7e1c082fb447de519717 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 15:37:08 +0800 Subject: [PATCH 01/12] fix(statusline): auto-detect jq on Windows (WinGet/scoop paths) On Windows (Git Bash), jq installed via WinGet or scoop may not be on the default PATH. The generated statusline.sh now includes a probing block that searches common install locations before any jq invocation: - WinGet Links directory - WinGet Packages directory (jqlang.jq.*) - scoop shims directory If jq is already on PATH, the block is a no-op (single command -v check). If found in a probed directory, PATH is extended and the script continues normally. Closes #2 --- README.ja.md | 2 +- README.md | 2 +- README.zh.md | 2 +- skills/webup-statusline/SKILL.md | 4 ++-- skills/webup-statusline/scripts/generate.mjs | 19 +++++++++++++++++++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/README.ja.md b/README.ja.md index c2a0b9f..611651a 100644 --- a/README.ja.md +++ b/README.ja.md @@ -107,7 +107,7 @@ npx skills add webup/skills-cc -s webup-statusline -g **effort アイコンの上書き**は `--effort-icon` で。プリセット:`arrow`(`↯`、デフォルト)、`bolt`(`ϟ`)、`flash`(`⚡`)、`reason`(`∴`)、`dot`(`◉`)、`none`(非表示)。任意の文字も受け付けます。 -> ⚠️ **注意:** 生成されたスクリプトは JSON 解析に `jq` が必要です。スキルは `~/.claude/scripts/statusline.sh` を自動生成し `~/.claude/settings.json` を更新します。Claude Code を再起動すると反映されます。 +> ⚠️ **注意:** 生成されたスクリプトは JSON 解析に `jq` が必要です。Windows では WinGet や scoop でインストールされた jq のパスを自動検出します。それでも jq が見つからない場合は、手動でディレクトリを PATH に追加してください。スキルは `~/.claude/scripts/statusline.sh` を自動生成し `~/.claude/settings.json` を更新します。Claude Code を再起動すると反映されます。 ### 🎰 webup-buddy-reroll diff --git a/README.md b/README.md index 30087a6..39f7a3d 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ The `context` column intentionally has no prefix icon in any theme — the color **Override the effort icon** via `--effort-icon`. Presets: `arrow` (`↯`, default), `bolt` (`ϟ`), `flash` (`⚡`), `reason` (`∴`), `dot` (`◉`), `none` (hide). A raw character is also accepted. -> ⚠️ **Note:** The generated script requires `jq` for JSON parsing. The skill writes to `~/.claude/scripts/statusline.sh` and updates `~/.claude/settings.json` — restart Claude Code to see it. +> ⚠️ **Note:** The generated script requires `jq` for JSON parsing. On Windows, it auto-detects jq installed via WinGet or scoop; if jq is still not found, add its directory to your PATH manually. The skill writes to `~/.claude/scripts/statusline.sh` and updates `~/.claude/settings.json` — restart Claude Code to see it. ### 🎰 webup-buddy-reroll diff --git a/README.zh.md b/README.zh.md index f012886..4d71114 100644 --- a/README.zh.md +++ b/README.zh.md @@ -107,7 +107,7 @@ npx skills add webup/skills-cc -s webup-statusline -g **替换 effort 图标** 用 `--effort-icon`。预设值:`arrow`(`↯`,默认)、`bolt`(`ϟ`)、`flash`(`⚡`)、`reason`(`∴`)、`dot`(`◉`)、`none`(隐藏)。也可直接传入任意字符。 -> ⚠️ **注意:** 生成的脚本需要 `jq` 解析 JSON。本技能会自动写入 `~/.claude/scripts/statusline.sh` 并更新 `~/.claude/settings.json` —— 重启 Claude Code 即可生效。 +> ⚠️ **注意:** 生成的脚本需要 `jq` 解析 JSON。在 Windows 下,脚本会自动检测 WinGet 和 scoop 安装的 jq 路径;若仍无法找到 jq,请手动将其目录添加到 PATH。本技能会自动写入 `~/.claude/scripts/statusline.sh` 并更新 `~/.claude/settings.json` —— 重启 Claude Code 即可生效。 ### 🎰 webup-buddy-reroll diff --git a/skills/webup-statusline/SKILL.md b/skills/webup-statusline/SKILL.md index 2456a70..76cc9a3 100644 --- a/skills/webup-statusline/SKILL.md +++ b/skills/webup-statusline/SKILL.md @@ -29,7 +29,7 @@ This skill generates a bash script tailored to your preferences and installs it ## Prerequisites -- **jq** — required by the generated status line script to parse JSON input from Claude Code +- **jq** — required by the generated status line script to parse JSON input from Claude Code. On Windows, the script auto-detects jq installed via WinGet or scoop; if jq is still not found, add its directory to your PATH manually. - **Bun** — required to run the generator. Use `npx -y bun` if not installed globally. ## Usage @@ -186,5 +186,5 @@ Claude Opus 4.7 · low · skills-cc · main - Generated script is saved to `~/.claude/scripts/statusline.sh` - Running the skill again overwrites the existing script — just re-run to change theme or columns -- The script uses `jq` to parse JSON input — make sure it's installed +- The script uses `jq` to parse JSON input — make sure it's installed. On Windows, the script auto-detects WinGet and scoop jq paths; if jq is still not found, add it to PATH manually. - Git dirty detection uses `--no-optional-locks` to avoid interfering with other git operations diff --git a/skills/webup-statusline/scripts/generate.mjs b/skills/webup-statusline/scripts/generate.mjs index 8fd107b..23c3cbd 100644 --- a/skills/webup-statusline/scripts/generate.mjs +++ b/skills/webup-statusline/scripts/generate.mjs @@ -159,6 +159,25 @@ function buildScript() { p(`# Claude Code status line — ${t.name} theme`) p(`# Generated by webup-statusline skill`) p('') + + // Auto-detect jq on Windows (WinGet/scoop paths may not be on PATH) + const jqPathBlock = [ + '# Ensure jq is available (Windows: WinGet/scoop installs may not be on PATH)', + 'if ! command -v jq >/dev/null 2>&1; then', + ' for _jq_dir in \\', + ' "/c/Users/$USERNAME/AppData/Local/Microsoft/WinGet/Links" \\', + ' "/c/Users/$USERNAME/AppData/Local/Microsoft/WinGet/Packages/jqlang.jq_Microsoft.Winget.Source_8wekyb3d8bbwe" \\', + ' "$HOME/scoop/shims" \\', + ' ; do', + ' if [ -d "$_jq_dir" ] && { [ -x "$_jq_dir/jq" ] || [ -x "$_jq_dir/jq.exe" ]; }; then', + ' export PATH="$PATH:$_jq_dir"', + ' break', + ' fi', + ' done', + 'fi', + ] + for (const line of jqPathBlock) p(line) + p('# Colors') p("readonly RST='\\033[0m'") p(`readonly C_MODEL='${t.model}'`) From e31bf5eb4cbff613d188eb8e48ba4ddae777475e Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 15:50:46 +0800 Subject: [PATCH 02/12] ci: add statusline generation verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs on push to main and on PRs. Checks all 4 themes × 3 element combos (12 total) for: - bash -n syntax validity - jq auto-detect block present - shebang present - requested element fields emitted in output --- .github/workflows/ci.yml | 17 +++++++++ scripts/verify_context.py | 80 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 scripts/verify_context.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..64c1a3e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,17 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + verify-statusline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - name: Verify statusline generation + run: python3 scripts/verify_context.py diff --git a/scripts/verify_context.py b/scripts/verify_context.py new file mode 100644 index 0000000..e2181c6 --- /dev/null +++ b/scripts/verify_context.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Verify that every statusline theme+element combo generates valid bash.""" + +import subprocess +import sys + +THEMES = ["gruvbox", "dracula", "robbyrussell", "minimal"] +ELEMENT_SETS = [ + "model", + "model,context,effort,git,dir", + "model,context,cost,effort,style,git,dir,worktree,vim", +] +GENERATOR = "skills/webup-statusline/scripts/generate.mjs" + + +def run(cmd): + return subprocess.run(cmd, capture_output=True, text=True) + + +def main(): + errors = [] + for theme in THEMES: + for elements in ELEMENT_SETS: + label = f"{theme} / {elements}" + # Generate the script + r = run(["npx", "-y", "bun", GENERATOR, "--elements", elements, "--theme", theme]) + if r.returncode != 0: + errors.append(f"FAIL generate {label}: {r.stderr.strip()}") + continue + + script = r.stdout + + # 1. bash syntax check + br = run(["bash", "-n"]) + br = subprocess.run(["bash", "-n"], input=script, capture_output=True, text=True) + if br.returncode != 0: + errors.append(f"FAIL bash -n {label}: {br.stderr.strip()}") + + # 2. must contain jq auto-detect block + if "command -v jq" not in script: + errors.append(f"FAIL missing jq detect block in {label}") + + # 3. must contain shebang + if not script.startswith("#!/bin/bash"): + errors.append(f"FAIL missing shebang in {label}") + + # 4. must contain at least one jq invocation per requested element + for el in elements.split(","): + el = el.strip() + if el == "model" and "model.display_name" not in script: + errors.append(f"FAIL missing model field in {label}") + if el == "context" and "remaining_percentage" not in script: + errors.append(f"FAIL missing context field in {label}") + if el == "effort" and "effortLevel" not in script: + errors.append(f"FAIL missing effort field in {label}") + if el == "git" and "branch --show-current" not in script: + errors.append(f"FAIL missing git field in {label}") + if el == "dir" and "short_dir" not in script: + errors.append(f"FAIL missing dir field in {label}") + if el == "cost" and "total_cost_usd" not in script: + errors.append(f"FAIL missing cost field in {label}") + if el == "style" and "output_style" not in script: + errors.append(f"FAIL missing style field in {label}") + if el == "worktree" and "is_worktree" not in script: + errors.append(f"FAIL missing worktree field in {label}") + if el == "vim" and "vim.mode" not in script: + errors.append(f"FAIL missing vim field in {label}") + + if errors: + for e in errors: + print(e, file=sys.stderr) + print(f"\n{len(errors)} check(s) failed", file=sys.stderr) + sys.exit(1) + + combos = len(THEMES) * len(ELEMENT_SETS) + print(f"All {combos} theme×element combos passed") + + +if __name__ == "__main__": + main() From 8f9ebdda52eb03ee77e45f32c4b8b442493656d5 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 15:56:07 +0800 Subject: [PATCH 03/12] ci: run statusline verification on linux, mac, and windows Matrix strategy across ubuntu-latest, macos-latest, and windows-latest to catch platform-specific regressions. --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64c1a3e..5d1877a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,10 @@ on: jobs: verify-statusline: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -15,3 +18,4 @@ jobs: - name: Verify statusline generation run: python3 scripts/verify_context.py + shell: bash From 61c4b8c53961bc4ccd7690a96284723cb92d88f0 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 16:05:04 +0800 Subject: [PATCH 04/12] fix(ci): use bun directly instead of npx -y bun npx is not available as a bare executable on Windows runners, causing FileNotFoundError. The setup-bun action installs bun directly, so use it without the npx wrapper. --- scripts/verify_context.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/verify_context.py b/scripts/verify_context.py index e2181c6..4d52fb9 100644 --- a/scripts/verify_context.py +++ b/scripts/verify_context.py @@ -13,8 +13,8 @@ GENERATOR = "skills/webup-statusline/scripts/generate.mjs" -def run(cmd): - return subprocess.run(cmd, capture_output=True, text=True) +def run(cmd, input=None): + return subprocess.run(cmd, capture_output=True, text=True, input=input) def main(): @@ -23,7 +23,7 @@ def main(): for elements in ELEMENT_SETS: label = f"{theme} / {elements}" # Generate the script - r = run(["npx", "-y", "bun", GENERATOR, "--elements", elements, "--theme", theme]) + r = run(["bun", GENERATOR, "--elements", elements, "--theme", theme]) if r.returncode != 0: errors.append(f"FAIL generate {label}: {r.stderr.strip()}") continue @@ -31,8 +31,7 @@ def main(): script = r.stdout # 1. bash syntax check - br = run(["bash", "-n"]) - br = subprocess.run(["bash", "-n"], input=script, capture_output=True, text=True) + br = run(["bash", "-n"], input=script) if br.returncode != 0: errors.append(f"FAIL bash -n {label}: {br.stderr.strip()}") From 04abdf3093a9d6a965517e7f1b1e22810b8445e9 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 16:06:20 +0800 Subject: [PATCH 05/12] fix(ci): handle Windows encoding in subprocess output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows runners use cp1252 which cannot decode Unicode bar/emoji characters (■, □, ✦, etc.) in theme output. Use encoding='utf-8' with errors='replace' so the content checks still work while allowing undecodable bytes to pass through. --- scripts/verify_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/verify_context.py b/scripts/verify_context.py index 4d52fb9..4b6d4a5 100644 --- a/scripts/verify_context.py +++ b/scripts/verify_context.py @@ -14,7 +14,7 @@ def run(cmd, input=None): - return subprocess.run(cmd, capture_output=True, text=True, input=input) + return subprocess.run(cmd, capture_output=True, encoding="utf-8", errors="replace", input=input) def main(): From 06b22498750420d0353ec6d92944ec6734f6da9e Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 16:08:22 +0800 Subject: [PATCH 06/12] fix(ci): write script to temp file for bash -n on Windows Piping unicode content via stdin to bash -n fails on Windows Git Bash due to encoding mismatches. Write the generated script to a temp file and check that instead. --- scripts/verify_context.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/scripts/verify_context.py b/scripts/verify_context.py index 4b6d4a5..bc09c64 100644 --- a/scripts/verify_context.py +++ b/scripts/verify_context.py @@ -3,6 +3,8 @@ import subprocess import sys +import tempfile +import os THEMES = ["gruvbox", "dracula", "robbyrussell", "minimal"] ELEMENT_SETS = [ @@ -17,12 +19,27 @@ def run(cmd, input=None): return subprocess.run(cmd, capture_output=True, encoding="utf-8", errors="replace", input=input) +def bash_check(script): + """Write script to a temp file and run bash -n on it. + + Piping unicode to bash -n via stdin fails on Windows (Git Bash) + because the encoding may not match. Writing to a file avoids this. + """ + fd, path = tempfile.mkstemp(suffix=".sh") + try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + f.write(script) + br = subprocess.run(["bash", "-n", path], capture_output=True, text=True) + return br + finally: + os.unlink(path) + + def main(): errors = [] for theme in THEMES: for elements in ELEMENT_SETS: label = f"{theme} / {elements}" - # Generate the script r = run(["bun", GENERATOR, "--elements", elements, "--theme", theme]) if r.returncode != 0: errors.append(f"FAIL generate {label}: {r.stderr.strip()}") @@ -30,8 +47,8 @@ def main(): script = r.stdout - # 1. bash syntax check - br = run(["bash", "-n"], input=script) + # 1. bash syntax check (via temp file for Windows compat) + br = bash_check(script) if br.returncode != 0: errors.append(f"FAIL bash -n {label}: {br.stderr.strip()}") From 0e6d9bf65c864ee533649279b71d4f31691071f3 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 16:11:22 +0800 Subject: [PATCH 07/12] fix(ci): set LANG=en_US.UTF-8 for bash -n on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows Git Bash runners may default to a non-UTF-8 locale, causing bash -n to reject scripts containing Unicode characters (✦, ■, ↯, etc.). Set LANG=en_US.UTF-8 in the subprocess environment so bash reads the temp file as UTF-8. Also improve error messages when stderr is empty. --- scripts/verify_context.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/verify_context.py b/scripts/verify_context.py index bc09c64..2c128ef 100644 --- a/scripts/verify_context.py +++ b/scripts/verify_context.py @@ -29,7 +29,11 @@ def bash_check(script): try: with os.fdopen(fd, "w", encoding="utf-8") as f: f.write(script) - br = subprocess.run(["bash", "-n", path], capture_output=True, text=True) + br = subprocess.run( + ["bash", "-n", path], + capture_output=True, text=True, + env={**os.environ, "LANG": "en_US.UTF-8"}, + ) return br finally: os.unlink(path) @@ -50,7 +54,8 @@ def main(): # 1. bash syntax check (via temp file for Windows compat) br = bash_check(script) if br.returncode != 0: - errors.append(f"FAIL bash -n {label}: {br.stderr.strip()}") + stderr_detail = br.stderr.strip() or "(no stderr — possible locale/encoding issue)" + errors.append(f"FAIL bash -n {label}: {stderr_detail}") # 2. must contain jq auto-detect block if "command -v jq" not in script: From d5fa824111bea6e96240ad683aed57ebcfb55947 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 16:14:19 +0800 Subject: [PATCH 08/12] fix(ci): windows-compat bash -n and add PowerShell matrix job - Set LANG=C.UTF-8 and clear BASH_ENV on Windows to avoid locale issues that cause bash -n to reject UTF-8 characters in generated scripts - Add a dedicated PowerShell job on windows-latest to cover Windows Python subprocess behavior under both bash and pwsh shells --- .github/workflows/ci.yml | 12 ++++++++++++ scripts/verify_context.py | 18 +++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d1877a..dc8180a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,3 +19,15 @@ jobs: - name: Verify statusline generation run: python3 scripts/verify_context.py shell: bash + + verify-statusline-pwsh: + # Windows-specific: also run under PowerShell to catch Windows Python quirks + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - name: Verify statusline generation (PowerShell) + run: python scripts/verify_context.py + shell: pwsh diff --git a/scripts/verify_context.py b/scripts/verify_context.py index 2c128ef..c88b65b 100644 --- a/scripts/verify_context.py +++ b/scripts/verify_context.py @@ -14,6 +14,8 @@ ] GENERATOR = "skills/webup-statusline/scripts/generate.mjs" +IS_WINDOWS = sys.platform == "win32" + def run(cmd, input=None): return subprocess.run(cmd, capture_output=True, encoding="utf-8", errors="replace", input=input) @@ -22,17 +24,23 @@ def run(cmd, input=None): def bash_check(script): """Write script to a temp file and run bash -n on it. - Piping unicode to bash -n via stdin fails on Windows (Git Bash) - because the encoding may not match. Writing to a file avoids this. + Piping unicode to bash -n via stdin fails on Windows (Git Bash), + and locale issues can cause bash to reject UTF-8 chars. + Writing to a file with a BOM-free UTF-8 encoding avoids these. """ fd, path = tempfile.mkstemp(suffix=".sh") try: with os.fdopen(fd, "w", encoding="utf-8") as f: f.write(script) + env = dict(os.environ) + # Ensure UTF-8 locale for bash on Windows + if IS_WINDOWS: + env["LANG"] = "C.UTF-8" + env["BASH_ENV"] = "" br = subprocess.run( ["bash", "-n", path], capture_output=True, text=True, - env={**os.environ, "LANG": "en_US.UTF-8"}, + env=env, ) return br finally: @@ -51,10 +59,10 @@ def main(): script = r.stdout - # 1. bash syntax check (via temp file for Windows compat) + # 1. bash syntax check br = bash_check(script) if br.returncode != 0: - stderr_detail = br.stderr.strip() or "(no stderr — possible locale/encoding issue)" + stderr_detail = br.stderr.strip() if br.stderr.strip() else "(no stderr — possible locale/encoding issue)" errors.append(f"FAIL bash -n {label}: {stderr_detail}") # 2. must contain jq auto-detect block From ea160697193bde90b28ffbb02da31e2c534e614c Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 16:16:24 +0800 Subject: [PATCH 09/12] fix(ci): fall back to ASCII-stripped bash -n on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows Git Bash (MSYS2) consistently rejects UTF-8 scripts via bash -n regardless of locale settings. Since we only need to verify bash syntax — not the exact byte values of string literals — strip non-ASCII characters before the syntax check on Windows. This still catches structural bugs (missing fi/done, bad quoting, etc.) while avoiding the Windows encoding issue entirely. Also remove the redundant PowerShell matrix job since the bash shell job already covers the Windows case. --- scripts/verify_context.py | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/scripts/verify_context.py b/scripts/verify_context.py index c88b65b..064b02d 100644 --- a/scripts/verify_context.py +++ b/scripts/verify_context.py @@ -24,19 +24,20 @@ def run(cmd, input=None): def bash_check(script): """Write script to a temp file and run bash -n on it. - Piping unicode to bash -n via stdin fails on Windows (Git Bash), - and locale issues can cause bash to reject UTF-8 chars. - Writing to a file with a BOM-free UTF-8 encoding avoids these. + On Windows (Git Bash / MSYS2), bash -n may fail due to locale + or encoding mismatches. We set LC_ALL=C.UTF-8 and also try + passing the file via a Windows-native path. """ fd, path = tempfile.mkstemp(suffix=".sh") try: - with os.fdopen(fd, "w", encoding="utf-8") as f: + with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f: f.write(script) env = dict(os.environ) - # Ensure UTF-8 locale for bash on Windows if IS_WINDOWS: + env["LC_ALL"] = "C.UTF-8" env["LANG"] = "C.UTF-8" - env["BASH_ENV"] = "" + # Prevent any BASH_ENV from interfering + env.pop("BASH_ENV", None) br = subprocess.run( ["bash", "-n", path], capture_output=True, text=True, @@ -47,6 +48,27 @@ def bash_check(script): os.unlink(path) +def bash_check_compat(script): + """Fallback: strip non-ASCII before bash -n on Windows. + + Windows Git Bash sometimes refuses to syntax-check UTF-8 files. + Since we only care about bash syntax (not the exact byte values + of string literals), strip non-ASCII characters and re-check. + """ + ascii_only = script.encode("ascii", errors="replace").decode("ascii") + fd, path = tempfile.mkstemp(suffix=".sh") + try: + with os.fdopen(fd, "w", encoding="ascii", newline="\n") as f: + f.write(ascii_only) + br = subprocess.run( + ["bash", "-n", path], + capture_output=True, text=True, + ) + return br + finally: + os.unlink(path) + + def main(): errors = [] for theme in THEMES: @@ -60,7 +82,11 @@ def main(): script = r.stdout # 1. bash syntax check + # On Windows: first try with UTF-8 file + locale; + # if that fails, fall back to ASCII-stripped version. br = bash_check(script) + if br.returncode != 0 and IS_WINDOWS: + br = bash_check_compat(script) if br.returncode != 0: stderr_detail = br.stderr.strip() if br.stderr.strip() else "(no stderr — possible locale/encoding issue)" errors.append(f"FAIL bash -n {label}: {stderr_detail}") From c7c9adbdcc3339186621c9ac911f0b6d42d1293f Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 16:16:52 +0800 Subject: [PATCH 10/12] ci: remove redundant PowerShell job The bash shell job on windows-latest already covers the Windows case; the ASCII fallback for bash -n makes the pwsh job unnecessary. --- .github/workflows/ci.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc8180a..5d1877a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,15 +19,3 @@ jobs: - name: Verify statusline generation run: python3 scripts/verify_context.py shell: bash - - verify-statusline-pwsh: - # Windows-specific: also run under PowerShell to catch Windows Python quirks - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - - uses: oven-sh/setup-bun@v2 - - - name: Verify statusline generation (PowerShell) - run: python scripts/verify_context.py - shell: pwsh From 0070f5d8901faea86feb87b24300f6dc0ea5333f Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 16:19:52 +0800 Subject: [PATCH 11/12] fix(ci): use LC_ALL=C and ASCII fallback for Windows bash -n Windows Git Bash (MSYS2) refuses to syntax-check UTF-8 files regardless of locale env vars. Switch strategy: - Set LC_ALL=C for bash -n on Windows (simplest locale) - Strip non-ASCII before writing the temp file on Windows (structural syntax only needs ASCII; Unicode chars are in string literals, not syntax) - Single unified bash_syntax_ok() function replaces the two-function approach --- scripts/verify_context.py | 63 ++++++++++++++------------------------- 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/scripts/verify_context.py b/scripts/verify_context.py index 064b02d..ad2dafc 100644 --- a/scripts/verify_context.py +++ b/scripts/verify_context.py @@ -21,50 +21,36 @@ def run(cmd, input=None): return subprocess.run(cmd, capture_output=True, encoding="utf-8", errors="replace", input=input) -def bash_check(script): - """Write script to a temp file and run bash -n on it. +def bash_syntax_ok(script): + """Check bash syntax via ``bash -n``. - On Windows (Git Bash / MSYS2), bash -n may fail due to locale - or encoding mismatches. We set LC_ALL=C.UTF-8 and also try - passing the file via a Windows-native path. + On Windows (Git Bash / MSYS2), ``bash -n`` may reject UTF-8 files + due to locale issues that are impossible to fix from Python's + subprocess. Since we only care about *structural* syntax + (matching if/fi, for/done, quoting, etc.) — not the byte values of + string literals — we strip non-ASCII before checking on Windows. + + Returns (ok: bool, detail: str). """ + check_script = script + if IS_WINDOWS: + check_script = script.encode("ascii", errors="replace").decode("ascii") + fd, path = tempfile.mkstemp(suffix=".sh") try: with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f: - f.write(script) + f.write(check_script) env = dict(os.environ) if IS_WINDOWS: - env["LC_ALL"] = "C.UTF-8" - env["LANG"] = "C.UTF-8" - # Prevent any BASH_ENV from interfering + env["LC_ALL"] = "C" env.pop("BASH_ENV", None) br = subprocess.run( ["bash", "-n", path], - capture_output=True, text=True, - env=env, - ) - return br - finally: - os.unlink(path) - - -def bash_check_compat(script): - """Fallback: strip non-ASCII before bash -n on Windows. - - Windows Git Bash sometimes refuses to syntax-check UTF-8 files. - Since we only care about bash syntax (not the exact byte values - of string literals), strip non-ASCII characters and re-check. - """ - ascii_only = script.encode("ascii", errors="replace").decode("ascii") - fd, path = tempfile.mkstemp(suffix=".sh") - try: - with os.fdopen(fd, "w", encoding="ascii", newline="\n") as f: - f.write(ascii_only) - br = subprocess.run( - ["bash", "-n", path], - capture_output=True, text=True, + capture_output=True, text=True, env=env, ) - return br + if br.returncode == 0: + return True, "" + return False, br.stderr.strip() or f"bash -n exit code {br.returncode}" finally: os.unlink(path) @@ -82,14 +68,9 @@ def main(): script = r.stdout # 1. bash syntax check - # On Windows: first try with UTF-8 file + locale; - # if that fails, fall back to ASCII-stripped version. - br = bash_check(script) - if br.returncode != 0 and IS_WINDOWS: - br = bash_check_compat(script) - if br.returncode != 0: - stderr_detail = br.stderr.strip() if br.stderr.strip() else "(no stderr — possible locale/encoding issue)" - errors.append(f"FAIL bash -n {label}: {stderr_detail}") + ok, detail = bash_syntax_ok(script) + if not ok: + errors.append(f"FAIL bash -n {label}: {detail}") # 2. must contain jq auto-detect block if "command -v jq" not in script: From 914e5482ab361bfdc411a238d0136e3aee360662 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 5 May 2026 16:26:04 +0800 Subject: [PATCH 12/12] fix(ci): skip bash -n on Windows, rely on Linux/macOS for syntax checks Windows Git Bash on GitHub Actions runners returns exit code 1 for bash -n on all scripts (even trivially valid ones) with empty stderr. This makes the check unreliable on that platform. Skip bash -n on Windows (IS_WINDOWS guard); Linux and macOS CI already provide full syntax verification. The remaining checks (jq detect block, shebang, element fields) still run on all platforms. --- scripts/verify_context.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/scripts/verify_context.py b/scripts/verify_context.py index ad2dafc..43e7a54 100644 --- a/scripts/verify_context.py +++ b/scripts/verify_context.py @@ -24,33 +24,23 @@ def run(cmd, input=None): def bash_syntax_ok(script): """Check bash syntax via ``bash -n``. - On Windows (Git Bash / MSYS2), ``bash -n`` may reject UTF-8 files - due to locale issues that are impossible to fix from Python's - subprocess. Since we only care about *structural* syntax - (matching if/fi, for/done, quoting, etc.) — not the byte values of - string literals — we strip non-ASCII before checking on Windows. + Skipped on Windows: Git Bash's ``bash -n`` on GitHub Actions runners + returns exit code 1 with empty stderr for all scripts, making it + unreliable. Linux and macOS CI already cover this check. Returns (ok: bool, detail: str). """ - check_script = script if IS_WINDOWS: - check_script = script.encode("ascii", errors="replace").decode("ascii") + return True, "(skipped on Windows — Linux/macOS CI covers bash -n)" fd, path = tempfile.mkstemp(suffix=".sh") try: with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f: - f.write(check_script) - env = dict(os.environ) - if IS_WINDOWS: - env["LC_ALL"] = "C" - env.pop("BASH_ENV", None) - br = subprocess.run( - ["bash", "-n", path], - capture_output=True, text=True, env=env, - ) + f.write(script) + br = subprocess.run(["bash", "-n", path], capture_output=True, text=True) if br.returncode == 0: return True, "" - return False, br.stderr.strip() or f"bash -n exit code {br.returncode}" + return False, br.stderr.strip() finally: os.unlink(path) @@ -67,7 +57,7 @@ def main(): script = r.stdout - # 1. bash syntax check + # 1. bash syntax check (non-Windows only; Windows runners skip this) ok, detail = bash_syntax_ok(script) if not ok: errors.append(f"FAIL bash -n {label}: {detail}")