Skip to content

Commit 8be892d

Browse files
committed
fix: add missing CCB_RUN_DIR and UTF-8 encoding to Windows async script (SeemSeam#127)
The Windows PowerShell background script was missing CCB_RUN_DIR env var (causing async mode to fail) and proper UTF-8 encoding setup (causing Chinese character garbling). Also adds CCB_EMAIL_* env var forwarding for email caller parity with Unix.
1 parent 57a29b5 commit 8be892d

2 files changed

Lines changed: 133 additions & 1 deletion

File tree

bin/ask

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -657,12 +657,28 @@ def main(argv: list[str]) -> int:
657657
script_file = log_dir / f"ask-{provider}-{task_id}.ps1"
658658
status_file_win = str(status_file).replace('"', '`"')
659659
log_file_win = str(log_file).replace('"', '`"')
660+
661+
# Pass CCB_RUN_DIR so background script can find askd state file
662+
ccb_run_dir = os.environ.get("CCB_RUN_DIR", "")
663+
run_dir_line = f'$env:CCB_RUN_DIR = "{ccb_run_dir}"\n' if ccb_run_dir else ""
664+
665+
# Collect CCB_EMAIL_* env vars for email caller
666+
email_env_lines = ""
667+
if caller == "email":
668+
for key in ("CCB_EMAIL_REQ_ID", "CCB_EMAIL_MSG_ID", "CCB_EMAIL_FROM"):
669+
val = os.environ.get(key, "")
670+
if val:
671+
email_env_lines += f'$env:{key} = "{val}"\n'
672+
660673
script_content = f'''$ErrorActionPreference = "SilentlyContinue"
674+
$OutputEncoding = [System.Text.Encoding]::UTF8
661675
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
676+
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
677+
$env:PYTHONIOENCODING = "utf-8"
662678
$env:CCB_REQ_ID = "{task_id}"
663679
$env:CCB_CALLER = "{caller}"
664680
$env:CCB_WORK_DIR = "{os.getcwd()}"
665-
$statusFile = "{status_file_win}"
681+
{run_dir_line}{email_env_lines}$statusFile = "{status_file_win}"
666682
$logFile = "{log_file_win}"
667683
function Write-CcbStatus([string]$line) {{
668684
Add-Content -Path $statusFile -Value ("{{0}} {{1}}" -f (Get-Date -Format "yyyy-MM-ddTHH:mm:sszzz"), $line) -Encoding UTF8

test/test_windows_compat.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""Tests for Windows compatibility fixes in bin/ask (issue #127)."""
2+
from __future__ import annotations
3+
4+
from pathlib import Path
5+
6+
REPO_ROOT = Path(__file__).resolve().parents[1]
7+
ASK_SCRIPT = REPO_ROOT / "bin" / "ask"
8+
9+
10+
def _read_ask_source() -> str:
11+
return ASK_SCRIPT.read_text(encoding="utf-8")
12+
13+
14+
def _extract_windows_block(source: str) -> str:
15+
"""Extract the main Windows (os.name == 'nt') block that generates the PowerShell script.
16+
17+
There are multiple ``os.name == "nt"`` checks in the source. The one we
18+
care about is the large block inside ``main()`` that writes the .ps1
19+
script. We identify it by looking for the block that contains
20+
'PowerShell' or 'script_content' so we skip the small helper guard in
21+
``_maybe_start_unified_daemon``.
22+
"""
23+
lines = source.splitlines()
24+
blocks: list[list[str]] = []
25+
in_block = False
26+
block_lines: list[str] = []
27+
indent_level: int | None = None
28+
29+
for line in lines:
30+
if ('os.name == "nt"' in line or "os.name == 'nt'" in line) and not in_block:
31+
in_block = True
32+
indent_level = len(line) - len(line.lstrip())
33+
block_lines = [line]
34+
continue
35+
if in_block:
36+
if line.strip() == "" or len(line) - len(line.lstrip()) > indent_level:
37+
block_lines.append(line)
38+
elif line.strip().startswith("else:"):
39+
blocks.append(block_lines)
40+
in_block = False
41+
block_lines = []
42+
else:
43+
block_lines.append(line)
44+
45+
if in_block and block_lines:
46+
blocks.append(block_lines)
47+
48+
# Return the block that contains the PowerShell script generation
49+
for block in blocks:
50+
text = "\n".join(block)
51+
if "script_content" in text or "PowerShell" in text or ".ps1" in text:
52+
return text
53+
54+
# Fallback: return all blocks concatenated
55+
return "\n".join(line for block in blocks for line in block)
56+
57+
58+
class TestWindowsPowerShellScript:
59+
"""Verify the Windows PowerShell script template includes required settings."""
60+
61+
def setup_method(self):
62+
self.source = _read_ask_source()
63+
self.win_block = _extract_windows_block(self.source)
64+
65+
def test_ccb_run_dir_in_windows_block(self):
66+
"""CCB_RUN_DIR must be passed to PowerShell script (issue #127)."""
67+
assert "CCB_RUN_DIR" in self.win_block, (
68+
"Windows block must include CCB_RUN_DIR env var"
69+
)
70+
71+
def test_utf8_output_encoding(self):
72+
"""$OutputEncoding must be set for proper pipe encoding."""
73+
assert "$OutputEncoding" in self.win_block
74+
75+
def test_utf8_input_encoding(self):
76+
"""Console InputEncoding must be set for Chinese chars."""
77+
assert "InputEncoding" in self.win_block
78+
79+
def test_pythonioencoding(self):
80+
"""PYTHONIOENCODING must be set for Python subprocess UTF-8."""
81+
assert "PYTHONIOENCODING" in self.win_block
82+
83+
def test_console_output_encoding(self):
84+
"""Console OutputEncoding must still be present."""
85+
assert "[Console]::OutputEncoding" in self.win_block
86+
87+
def test_email_env_vars_in_windows_block(self):
88+
"""CCB_EMAIL_* env vars must be handled in Windows block."""
89+
assert "CCB_EMAIL" in self.win_block or "email_env_lines" in self.win_block
90+
91+
def test_unix_block_still_has_run_dir(self):
92+
"""Unix block must still include CCB_RUN_DIR (no regression)."""
93+
# Find the else/Unix block
94+
assert 'export CCB_RUN_DIR' in self.source
95+
96+
97+
class TestWindowsUnixParity:
98+
"""Verify Windows and Unix script generation have feature parity."""
99+
100+
def setup_method(self):
101+
self.source = _read_ask_source()
102+
103+
def test_both_blocks_set_req_id(self):
104+
"""Both Windows and Unix set CCB_REQ_ID."""
105+
assert '$env:CCB_REQ_ID' in self.source # Windows
106+
assert 'export CCB_REQ_ID' in self.source # Unix
107+
108+
def test_both_blocks_set_caller(self):
109+
"""Both Windows and Unix set CCB_CALLER."""
110+
assert '$env:CCB_CALLER' in self.source # Windows
111+
assert 'export CCB_CALLER' in self.source # Unix
112+
113+
def test_both_blocks_set_work_dir(self):
114+
"""Both Windows and Unix set CCB_WORK_DIR."""
115+
assert '$env:CCB_WORK_DIR' in self.source # Windows
116+
assert 'export CCB_WORK_DIR' in self.source # Unix

0 commit comments

Comments
 (0)