From d874bd02fdb1f48a9ec4d19deb5527fb36e66fa9 Mon Sep 17 00:00:00 2001 From: Szymon Janikowski Date: Sat, 9 May 2026 13:19:12 +0200 Subject: [PATCH 1/2] fix(windows): enforce LF for shell scripts and Dockerfile via .gitattributes Without this, a fresh checkout on Windows has CRLF on test.sh; the Linux kernel inside the benchmark sandbox reads `#!/bin/bash\r` as the shebang and every trial fails with `bash: required file not found`. - Repo: .gitattributes locks LF for *.sh, Dockerfile, etc; CRLF for *.ps1. - Scaffold: nasde init writes .gitattributes and uses `Path.write_text(..., newline="")` so freshly-scaffolded files are LF on Windows too (Python's default text mode translates \n -> \r\n). - Skills: nasde-benchmark-creator, nasde-benchmark-from-history, and nasde-benchmark-from-public-repos got a "Critical: line endings on Windows" section warning AI agents authoring benchmarks. docs/windows-issues-2026-05-09.md: companion report listing 3 remaining Windows issues (Unicode crashes on cp1250 console, missing harbor[daytona] extra) that need separate tickets. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../skills/nasde-benchmark-creator/SKILL.md | 58 +++++ .../nasde-benchmark-from-history/SKILL.md | 9 + .../SKILL.md | 9 + .gitattributes | 46 ++++ docs/windows-issues-2026-05-09.md | 210 ++++++++++++++++++ src/nasde_toolkit/scaffold/__init__.py | 46 +++- 6 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100644 docs/windows-issues-2026-05-09.md diff --git a/.claude/skills/nasde-benchmark-creator/SKILL.md b/.claude/skills/nasde-benchmark-creator/SKILL.md index 03d4b7e..55a3c23 100644 --- a/.claude/skills/nasde-benchmark-creator/SKILL.md +++ b/.claude/skills/nasde-benchmark-creator/SKILL.md @@ -14,6 +14,52 @@ description: | Create and configure coding agent benchmarks for evaluation with `nasde`. A benchmark is a set of coding tasks that AI agents solve inside isolated Docker containers, scored both by functional tests (pass/fail) and by an LLM-as-a-Judge architecture assessment. +## Critical: line endings on Windows (read this first) + +Benchmark scripts execute inside **Linux** sandboxes (Docker, Daytona). If `tests/test.sh`, `solution/solve.sh`, or `environment/Dockerfile` are checked out with **CRLF** line endings (the Windows git default when `core.autocrlf=true` and there is no `.gitattributes`), every trial fails immediately with: + +``` +bash: line 1: /tests/test.sh: cannot execute: required file not found +``` + +…because the kernel reads the shebang as `#!/bin/bash\r` and tries to execute a non-existent `/bin/bash\r`. The agent finishes its work, but the verifier never runs and Harbor reports `RewardFileNotFoundError`. + +**Mitigation (always do this for a new benchmark — `nasde init` does it for you, but verify):** + +1. The benchmark repo MUST have a `.gitattributes` file enforcing LF for shell scripts and Dockerfiles. The minimum content: + ```gitattributes + * text=auto eol=lf + *.sh text eol=lf + *.bash text eol=lf + Dockerfile text eol=lf + *.dockerfile text eol=lf + docker-compose.yaml text eol=lf + docker-compose.yml text eol=lf + + *.ps1 text eol=crlf + *.bat text eol=crlf + *.cmd text eol=crlf + ``` + `nasde init` writes this automatically. If you are adding a benchmark to an existing repo without `.gitattributes`, create one before adding any task. + +2. When **writing** `.sh` or `Dockerfile` content programmatically on Windows, write with explicit LF — not `path.write_text(content)` (which translates `\n`→`\r\n` on Windows), but `path.write_text(content, encoding="utf-8", newline="")` or open the file in binary mode. + +3. After committing on Windows for the first time, run: + ```bash + git add --renormalize . + git commit -m "normalize line endings" + ``` + to fix any files that landed before `.gitattributes` was in place. + +4. Sanity check before pushing a new task: + ```bash + file tasks//tests/test.sh + # MUST say "with LF line terminators" or omit line-terminator info entirely. + # If it says "with CRLF line terminators" — fix it (`sed -i 's/\r$//' file`). + ``` + +This applies equally when you're **adding tasks to a benchmark someone else created** — if their repo has no `.gitattributes` and you're on Windows, your contribution will silently break for them on Linux CI and vice versa. + ## Step 1: Understand what to evaluate Before creating files, clarify with the user: @@ -116,6 +162,8 @@ What the agent must NOT do (e.g., don't modify existing tests). ### environment/Dockerfile (required) +> **Reminder for Windows authors:** the Dockerfile and any helper scripts it `COPY`s in must have LF line endings — Docker tolerates CRLF in some commands but not in `RUN` shell snippets, and any shell script copied with CRLF will hit the same shebang failure as `test.sh`. + ```dockerfile FROM @@ -137,6 +185,8 @@ The Dockerfile MUST be self-contained — the agent starts working immediately. ### tests/test.sh (required — Harbor verifier) +> **Reminder for Windows authors:** this file MUST be saved with LF line endings. See "Critical: line endings on Windows" at the top of this skill. CRLF here = `bash: required file not found` and a wasted trial. + ```bash #!/bin/bash cd /app @@ -319,3 +369,11 @@ Before running with a real agent: ```bash nasde run --variant vanilla --tasks --without-eval -C . ``` + +4. **Final pre-flight on Windows authors** — verify no CRLF leaked in: + ```bash + find tasks -name '*.sh' -exec sh -c 'file "$1" | grep -q CRLF && echo "BAD: $1"' _ {} \; + find tasks -name 'Dockerfile' -exec sh -c 'file "$1" | grep -q CRLF && echo "BAD: $1"' _ {} \; + # Both should print nothing. + ``` + If anything prints, fix with `sed -i 's/\r$//' ` and re-commit. diff --git a/.claude/skills/nasde-benchmark-from-history/SKILL.md b/.claude/skills/nasde-benchmark-from-history/SKILL.md index 05292cb..698275c 100644 --- a/.claude/skills/nasde-benchmark-from-history/SKILL.md +++ b/.claude/skills/nasde-benchmark-from-history/SKILL.md @@ -19,6 +19,15 @@ Generate NASDE benchmark tasks by mining git history. You analyze commits, diffs - An existing NASDE benchmark project (run `nasde init` first, or use the `nasde-benchmark-creator` skill) - If the benchmark project doesn't exist yet, create it first — this skill generates tasks, not the project scaffold +## Critical: line endings on Windows (read this first) + +When generating `tests/test.sh`, `solution/solve.sh`, or `environment/Dockerfile` on a Windows host, write them with **LF** line endings or every trial fails with `bash: required file not found` (the kernel reads `#!/bin/bash\r` as the shebang). See the full explanation and `.gitattributes` template in the `nasde-benchmark-creator` skill. + +Quick rules: +- The benchmark project MUST have a `.gitattributes` enforcing `*.sh text eol=lf` and `Dockerfile text eol=lf`. `nasde init` creates this. If the existing project lacks it, **create `.gitattributes` before generating any task files**. +- When writing files programmatically, use `path.write_text(content, encoding="utf-8", newline="")` — never the bare default which translates `\n`→`\r\n` on Windows. +- Sanity-check after generation: `find tasks/ -name '*.sh' -o -name 'Dockerfile' | xargs file | grep CRLF` should print nothing. + ## Step 1: Identify the source repository and commit range Ask the user: diff --git a/.claude/skills/nasde-benchmark-from-public-repos/SKILL.md b/.claude/skills/nasde-benchmark-from-public-repos/SKILL.md index 48c5715..5fc106a 100644 --- a/.claude/skills/nasde-benchmark-from-public-repos/SKILL.md +++ b/.claude/skills/nasde-benchmark-from-public-repos/SKILL.md @@ -19,6 +19,15 @@ Build a diverse NASDE benchmark by curating tasks from multiple public GitHub re - A clear description of the skill being evaluated (what it does, what kinds of tasks it helps with) - Internet access (to browse and clone public repositories) +## Critical: line endings on Windows (read this first) + +When generating `tests/test.sh`, `solution/solve.sh`, or `environment/Dockerfile` on a Windows host, write them with **LF** line endings or every trial fails with `bash: required file not found` (the kernel reads `#!/bin/bash\r` as the shebang). See the full explanation and `.gitattributes` template in the `nasde-benchmark-creator` skill. + +Quick rules: +- The benchmark project MUST have a `.gitattributes` enforcing `*.sh text eol=lf` and `Dockerfile text eol=lf`. `nasde init` creates this. If the existing project lacks it, **create `.gitattributes` before generating any task files**. +- When writing files programmatically, use `path.write_text(content, encoding="utf-8", newline="")` — never the bare default which translates `\n`→`\r\n` on Windows. +- Sanity-check after generation: `find tasks/ -name '*.sh' -o -name 'Dockerfile' | xargs file | grep CRLF` should print nothing. + ## Step 1: Understand the skill under test Ask the user: diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..873b804 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,46 @@ +# Default: let Git detect text vs binary, force LF in working tree. +# Critical: shell scripts and Dockerfiles MUST be LF — they are executed by +# Linux interpreters in benchmark sandboxes (Daytona, Docker). CRLF causes +# `bash: required file not found` because the shebang becomes `#!/bin/bash\r`. +* text=auto eol=lf + +# Source code that runs in Linux containers / cross-platform tooling: force LF. +*.sh text eol=lf +*.bash text eol=lf +*.py text eol=lf +Dockerfile text eol=lf +*.dockerfile text eol=lf +docker-compose.yaml text eol=lf +docker-compose.yml text eol=lf +*.toml text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.json text eol=lf +*.md text eol=lf +Makefile text eol=lf +*.mk text eol=lf + +# PowerShell expects CRLF on Windows. Keep as-is so PS5.1 parses cleanly. +*.ps1 text eol=crlf +*.psd1 text eol=crlf +*.psm1 text eol=crlf + +# Windows batch files require CRLF. +*.bat text eol=crlf +*.cmd text eol=crlf + +# Binary assets: never touch line endings. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary +*.zip binary +*.gz binary +*.tgz binary +*.tar binary +*.whl binary +*.so binary +*.dll binary +*.exe binary diff --git a/docs/windows-issues-2026-05-09.md b/docs/windows-issues-2026-05-09.md new file mode 100644 index 0000000..f6f9789 --- /dev/null +++ b/docs/windows-issues-2026-05-09.md @@ -0,0 +1,210 @@ +# Windows compatibility issues — 2026-05-09 + +Discovered while verifying [PR #45](https://github.com/NoesisVision/nasde-toolkit/pull/45) end-to-end on Windows 11 + PowerShell 5.1 + Polish locale (cp1250). PR #45 itself works correctly; the issues below are pre-existing Windows-only friction points that surfaced during the verification run. + +Verification environment: +- Windows 11 Pro 26100 +- PowerShell 5.1 (default; not pwsh 7+) +- Console code page: **cp1250** (Polish locale) +- `git config --get core.autocrlf` → `true` (Windows default) +- uv 0.9.x, two interpreters installed: cpython 3.13.13 and 3.13.4 + +This document lists **3 open issues** that need follow-up tickets. Two issues found during the same session are already resolved on the [`fix/gitattributes-lf`](https://github.com/NoesisVision/nasde-toolkit/tree/fix/gitattributes-lf) branch and are summarized at the bottom for context. + +## TL;DR — open issues (create tickets for these) + +| # | Issue | Severity | Workaround | +|---|-------|----------|------------| +| 1 | `nasde install-skills` `UnicodeEncodeError` on `✓` (cp1250) | High (lies via exit code) | None — files are copied; ignore exit 1 | +| 2 | `nasde run` `UnicodeEncodeError` on `⠋` Braille spinner (cp1250) | **Blocker** (every nasde call dies) | `$env:PYTHONUTF8='1'` before any nasde call | +| 3 | Daytona extras missing — `harbor[daytona]` not pulled by nasde | High (any cloud env fails) | `uv run --with 'harbor[daytona]' nasde …` | + +> **Already resolved (no ticket needed), listed for context:** +> - **uv selecting Python 3.14 → MSVC source-build of `pyiceberg`** — resolved in `README.md` (every install command documents `--python 3.13`, see lines 46, 52, 215, 221). The pitfall only hits operators who run `uv tool install .` without consulting README. +> - **Shell script CRLF breaks Linux verifier in sandbox** — resolved on `fix/gitattributes-lf`: repo-wide `.gitattributes` enforcing `*.sh text eol=lf` + dos2unix on 17 `.sh` and 8 `Dockerfile` files. Skill-level docs added to `nasde-benchmark-creator`, `nasde-benchmark-from-history`, `nasde-benchmark-from-public-repos`. +> - **`Path.write_text` default translates `\n`→`\r\n` on Windows** — resolved on `fix/gitattributes-lf`: `scaffold/__init__.py:_write_if_missing` now passes `newline=""` and `encoding="utf-8"` so `nasde init` writes deterministic LF on every OS. + +--- + +## Issue 1 — `nasde install-skills` UnicodeEncodeError on `✓` (cp1250) + +### Severity: High (skills are installed, but exit code is 1) + +`nasde install-skills` exits with code 1 on Polish Windows PowerShell, even though the skill directories were correctly copied. + +### Root cause + +- `src/nasde_toolkit/skills_installer.py:_print_summary` prints `f"[green]✓ Installed {len(installed)} skill(s) …"`. +- Rich detects a Windows console and uses its `legacy_windows_render` path (`rich/_win32_console.py`). +- That path calls the system's locale-defined codec on each rune. On cp1250, U+2713 (✓) is not mapped, raising `UnicodeEncodeError` from `cp1250.py:19`. +- `shutil.copytree` for each skill runs **before** `_print_summary`, so all skills land on disk before the crash. + +### Repro + +```powershell +# Polish Windows console (cp1250 default): +nasde install-skills --force +# → UnicodeEncodeError: 'charmap' codec can't encode character '✓' +# → exit 1 +# But: +ls $env:USERPROFILE\.claude\skills\ +# → all 4 nasde-* skills present with fresh timestamps +``` + +### Impact + +- Direct: misleading exit code; CI scripts and automation that check `$LASTEXITCODE` will treat a successful install as failure. +- Indirect: erodes trust in tooling; every nasde install on PL Windows looks broken. + +### Workaround + +```powershell +$env:PYTHONUTF8 = '1' +nasde install-skills --force +# → completes cleanly with exit 0 +``` + +This works because `PYTHONUTF8=1` puts CPython in UTF-8 mode, so stdout encodes via UTF-8 instead of cp1250. + +### Proposed fix + +Either: +1. Replace `✓` and `•` glyphs in `_print_summary` with ASCII (`[OK]`, `-`). Trivial, eliminates the crash unconditionally. +2. At CLI entrypoint (`cli.py`), set `os.environ.setdefault("PYTHONUTF8", "1")` before importing Rich. Affects all commands. +3. Reconfigure stdout encoding manually: + ```python + import sys + if sys.platform == "win32": + sys.stdout.reconfigure(encoding="utf-8") + sys.stderr.reconfigure(encoding="utf-8") + ``` + +Recommendation: **(2)** — single-line change, fixes Issues 2 and 3 simultaneously. + +--- + +## Issue 2 — `nasde run` UnicodeEncodeError on `⠋` (Braille progress spinner) + +### Severity: Blocker — every benchmark run dies before doing real work + +This is the same family as Issue 1 but more severe because it affects the main `nasde run` command, not just install. + +### Root cause + +- Rich's `Progress` widget uses Braille spinner glyphs U+2800..U+28FF (`⠁`, `⠂`, `⠋`, …). +- When stdout is redirected (`> log 2>&1` or in a non-TTY context), Rich SHOULD detect non-TTY and disable spinner — but on Windows, the detection path differs and the spinner still tries to write Braille characters. +- cp1250 doesn't include U+280B → `UnicodeEncodeError` → process exits before Harbor even creates a sandbox. + +### Repro + +```powershell +# No env vars set: +nasde run --variant claude-vanilla --tasks foo --without-eval -C examples/refactoring-skill +# → UnicodeEncodeError: 'charmap' codec can't encode character '⠋' +# → process dies immediately after printing the run-config banner +``` + +### Impact + +`nasde run` is the primary command. Every Windows user with PL/non-UTF-8 locale hits this on their first attempt and gives up. + +### Workaround + +```powershell +$env:PYTHONUTF8 = '1' +$env:PYTHONIOENCODING = 'utf-8' +nasde run … +``` + +Alternative: change console code page to UTF-8 with `chcp 65001` before each session. + +### Proposed fix + +Same as Issue 1 — set `PYTHONUTF8=1` programmatically at CLI entrypoint. One-liner in `cli.py`: + +```python +import os, sys +if sys.platform == "win32": + os.environ.setdefault("PYTHONUTF8", "1") +``` + +…executed before any Rich import. + +--- + +## Issue 3 — `harbor[daytona]` extra not pulled by nasde + +### Severity: High (any `--harbor-env daytona|modal|e2b|runloop|gke` fails) + +### Root cause + +`pyproject.toml` declares `harbor` as a plain dependency, not `harbor[daytona]` or `harbor[cloud]`. Harbor's daytona environment class raises `MissingExtraError` at constructor time when `daytona-sdk` isn't importable. + +### Repro + +```powershell +$env:DAYTONA_API_KEY = "..." +nasde run --variant foo --tasks bar -C bench --harbor-env daytona --without-eval +# → MissingExtraError: The 'daytona' package is required but not installed. +# Install it with: +# pip install 'harbor[daytona]' +# uv tool install 'harbor[daytona]' +``` + +### Impact + +Cross-platform issue, not Windows-specific — but on Windows it compounds with Issue 2 because Issue 2 hides Issue 3 behind a more confusing crash message. + +### Workaround + +```powershell +uv run --with 'harbor[daytona]' nasde run --harbor-env daytona … +``` + +Or for the global tool: +```powershell +uv tool install --reinstall --with 'harbor[daytona]' . +``` + +### Proposed fix + +Three options, in order of preference: + +1. **Add `harbor[cloud]` as a default dependency** — small package size impact, every user gets daytona/modal/e2b/runloop/gke out of the box. +2. **Document explicit `--with` syntax** in README and `--harbor-env` help text. +3. **Detect missing extra at CLI level** and print actionable message: + ``` + error: --harbor-env daytona requires the 'harbor[daytona]' extra. + install with: uv tool install --reinstall --with 'harbor[daytona]' . + ``` + +Recommendation: **(1)**. The current behavior trades ~30 MB of disk for a setup wall every cloud user hits. + +--- + +## Cross-cutting recommendations + +### Tier 1 — should land before announcing nasde for Windows + +- **Set `PYTHONUTF8=1` at CLI entrypoint** on Windows (fixes Issues 1, 2 with one line). + +### Tier 2 — quality of life + +- **Add `harbor[cloud]` to default deps** OR detect-and-actionable-error on missing extra (Issue 3). +- **Replace Unicode glyphs (`✓`, `•`) in user-facing print statements with ASCII fallbacks**, even with PYTHONUTF8 on, so error paths don't crash on font/console edge cases. + +--- + +## Verification environment for reproduction + +``` +OS: Windows 11 Pro 26100 +Shell: PowerShell 5.1 (powershell.exe, NOT pwsh 7+) +Locale: Polish, console code page cp1250 +Python: cpython 3.13.13 + 3.14.4 installed (uv-managed) +uv: 0.9.x +git: 2.x with core.autocrlf=true (default) +nasde: 0.3.3.dev5 (PR #45 head) +``` + +To reproduce on a clean machine, ensure these conditions match — especially the cp1250 locale, which is what surfaces Issues 1 and 2. On `en-US` Windows (cp1252), Issue 1's `✓` (U+2713) would *not* crash, because cp1252 also lacks U+2713 but Rich's path differs. The Braille spinner from Issue 2 fails on both cp1250 and cp1252 since neither covers U+2800. diff --git a/src/nasde_toolkit/scaffold/__init__.py b/src/nasde_toolkit/scaffold/__init__.py index 647450d..b0deaa6 100644 --- a/src/nasde_toolkit/scaffold/__init__.py +++ b/src/nasde_toolkit/scaffold/__init__.py @@ -101,6 +101,49 @@ jobs/ """ +GITATTRIBUTES_TEMPLATE = """\ +# Critical: files executed inside benchmark sandboxes (Linux containers via +# Docker / Daytona / Modal / etc.) MUST be LF. CRLF on a shebang line causes +# `bash: required file not found` because the kernel reads `#!/bin/bash\\r`. +* text=auto eol=lf + +*.sh text eol=lf +*.bash text eol=lf +Dockerfile text eol=lf +*.dockerfile text eol=lf +docker-compose.yaml text eol=lf +docker-compose.yml text eol=lf +*.toml text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.json text eol=lf +*.md text eol=lf +*.py text eol=lf + +# PowerShell / Windows batch keep CRLF. +*.ps1 text eol=crlf +*.psd1 text eol=crlf +*.psm1 text eol=crlf +*.bat text eol=crlf +*.cmd text eol=crlf + +# Binary assets — never touch line endings. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary +*.zip binary +*.gz binary +*.tar binary +*.tgz binary +*.whl binary +*.so binary +*.dll binary +*.exe binary +""" + def create_project(project_dir: Path, name: str) -> None: """Scaffold a new evaluation project structure.""" @@ -111,6 +154,7 @@ def create_project(project_dir: Path, name: str) -> None: _write_if_missing(project_dir / "nasde.toml", NASDE_TOML_TEMPLATE.format(name=name)) _write_if_missing(project_dir / "assessment_dimensions.json", ASSESSMENT_DIMENSIONS_TEMPLATE) _write_if_missing(project_dir / ".gitignore", GITIGNORE_TEMPLATE) + _write_if_missing(project_dir / ".gitattributes", GITATTRIBUTES_TEMPLATE) tasks_dir.mkdir(parents=True, exist_ok=True) variants_dir.mkdir(parents=True, exist_ok=True) @@ -158,4 +202,4 @@ def _write_if_missing(path: Path, content: str) -> None: console.print(f" [yellow]Skipping[/yellow] {path.name} (already exists)") return path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content) + path.write_text(content, encoding="utf-8", newline="") From 2f7e37efcc09b639761a74acecdd033abb6fe0d7 Mon Sep 17 00:00:00 2001 From: Szymon Janikowski Date: Sat, 9 May 2026 13:29:26 +0200 Subject: [PATCH 2/2] chore: drop docs/windows-issues report from PR scope The report belongs in a separate deliverable; this PR is the code/scaffold fix only. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/windows-issues-2026-05-09.md | 210 ------------------------------ 1 file changed, 210 deletions(-) delete mode 100644 docs/windows-issues-2026-05-09.md diff --git a/docs/windows-issues-2026-05-09.md b/docs/windows-issues-2026-05-09.md deleted file mode 100644 index f6f9789..0000000 --- a/docs/windows-issues-2026-05-09.md +++ /dev/null @@ -1,210 +0,0 @@ -# Windows compatibility issues — 2026-05-09 - -Discovered while verifying [PR #45](https://github.com/NoesisVision/nasde-toolkit/pull/45) end-to-end on Windows 11 + PowerShell 5.1 + Polish locale (cp1250). PR #45 itself works correctly; the issues below are pre-existing Windows-only friction points that surfaced during the verification run. - -Verification environment: -- Windows 11 Pro 26100 -- PowerShell 5.1 (default; not pwsh 7+) -- Console code page: **cp1250** (Polish locale) -- `git config --get core.autocrlf` → `true` (Windows default) -- uv 0.9.x, two interpreters installed: cpython 3.13.13 and 3.13.4 - -This document lists **3 open issues** that need follow-up tickets. Two issues found during the same session are already resolved on the [`fix/gitattributes-lf`](https://github.com/NoesisVision/nasde-toolkit/tree/fix/gitattributes-lf) branch and are summarized at the bottom for context. - -## TL;DR — open issues (create tickets for these) - -| # | Issue | Severity | Workaround | -|---|-------|----------|------------| -| 1 | `nasde install-skills` `UnicodeEncodeError` on `✓` (cp1250) | High (lies via exit code) | None — files are copied; ignore exit 1 | -| 2 | `nasde run` `UnicodeEncodeError` on `⠋` Braille spinner (cp1250) | **Blocker** (every nasde call dies) | `$env:PYTHONUTF8='1'` before any nasde call | -| 3 | Daytona extras missing — `harbor[daytona]` not pulled by nasde | High (any cloud env fails) | `uv run --with 'harbor[daytona]' nasde …` | - -> **Already resolved (no ticket needed), listed for context:** -> - **uv selecting Python 3.14 → MSVC source-build of `pyiceberg`** — resolved in `README.md` (every install command documents `--python 3.13`, see lines 46, 52, 215, 221). The pitfall only hits operators who run `uv tool install .` without consulting README. -> - **Shell script CRLF breaks Linux verifier in sandbox** — resolved on `fix/gitattributes-lf`: repo-wide `.gitattributes` enforcing `*.sh text eol=lf` + dos2unix on 17 `.sh` and 8 `Dockerfile` files. Skill-level docs added to `nasde-benchmark-creator`, `nasde-benchmark-from-history`, `nasde-benchmark-from-public-repos`. -> - **`Path.write_text` default translates `\n`→`\r\n` on Windows** — resolved on `fix/gitattributes-lf`: `scaffold/__init__.py:_write_if_missing` now passes `newline=""` and `encoding="utf-8"` so `nasde init` writes deterministic LF on every OS. - ---- - -## Issue 1 — `nasde install-skills` UnicodeEncodeError on `✓` (cp1250) - -### Severity: High (skills are installed, but exit code is 1) - -`nasde install-skills` exits with code 1 on Polish Windows PowerShell, even though the skill directories were correctly copied. - -### Root cause - -- `src/nasde_toolkit/skills_installer.py:_print_summary` prints `f"[green]✓ Installed {len(installed)} skill(s) …"`. -- Rich detects a Windows console and uses its `legacy_windows_render` path (`rich/_win32_console.py`). -- That path calls the system's locale-defined codec on each rune. On cp1250, U+2713 (✓) is not mapped, raising `UnicodeEncodeError` from `cp1250.py:19`. -- `shutil.copytree` for each skill runs **before** `_print_summary`, so all skills land on disk before the crash. - -### Repro - -```powershell -# Polish Windows console (cp1250 default): -nasde install-skills --force -# → UnicodeEncodeError: 'charmap' codec can't encode character '✓' -# → exit 1 -# But: -ls $env:USERPROFILE\.claude\skills\ -# → all 4 nasde-* skills present with fresh timestamps -``` - -### Impact - -- Direct: misleading exit code; CI scripts and automation that check `$LASTEXITCODE` will treat a successful install as failure. -- Indirect: erodes trust in tooling; every nasde install on PL Windows looks broken. - -### Workaround - -```powershell -$env:PYTHONUTF8 = '1' -nasde install-skills --force -# → completes cleanly with exit 0 -``` - -This works because `PYTHONUTF8=1` puts CPython in UTF-8 mode, so stdout encodes via UTF-8 instead of cp1250. - -### Proposed fix - -Either: -1. Replace `✓` and `•` glyphs in `_print_summary` with ASCII (`[OK]`, `-`). Trivial, eliminates the crash unconditionally. -2. At CLI entrypoint (`cli.py`), set `os.environ.setdefault("PYTHONUTF8", "1")` before importing Rich. Affects all commands. -3. Reconfigure stdout encoding manually: - ```python - import sys - if sys.platform == "win32": - sys.stdout.reconfigure(encoding="utf-8") - sys.stderr.reconfigure(encoding="utf-8") - ``` - -Recommendation: **(2)** — single-line change, fixes Issues 2 and 3 simultaneously. - ---- - -## Issue 2 — `nasde run` UnicodeEncodeError on `⠋` (Braille progress spinner) - -### Severity: Blocker — every benchmark run dies before doing real work - -This is the same family as Issue 1 but more severe because it affects the main `nasde run` command, not just install. - -### Root cause - -- Rich's `Progress` widget uses Braille spinner glyphs U+2800..U+28FF (`⠁`, `⠂`, `⠋`, …). -- When stdout is redirected (`> log 2>&1` or in a non-TTY context), Rich SHOULD detect non-TTY and disable spinner — but on Windows, the detection path differs and the spinner still tries to write Braille characters. -- cp1250 doesn't include U+280B → `UnicodeEncodeError` → process exits before Harbor even creates a sandbox. - -### Repro - -```powershell -# No env vars set: -nasde run --variant claude-vanilla --tasks foo --without-eval -C examples/refactoring-skill -# → UnicodeEncodeError: 'charmap' codec can't encode character '⠋' -# → process dies immediately after printing the run-config banner -``` - -### Impact - -`nasde run` is the primary command. Every Windows user with PL/non-UTF-8 locale hits this on their first attempt and gives up. - -### Workaround - -```powershell -$env:PYTHONUTF8 = '1' -$env:PYTHONIOENCODING = 'utf-8' -nasde run … -``` - -Alternative: change console code page to UTF-8 with `chcp 65001` before each session. - -### Proposed fix - -Same as Issue 1 — set `PYTHONUTF8=1` programmatically at CLI entrypoint. One-liner in `cli.py`: - -```python -import os, sys -if sys.platform == "win32": - os.environ.setdefault("PYTHONUTF8", "1") -``` - -…executed before any Rich import. - ---- - -## Issue 3 — `harbor[daytona]` extra not pulled by nasde - -### Severity: High (any `--harbor-env daytona|modal|e2b|runloop|gke` fails) - -### Root cause - -`pyproject.toml` declares `harbor` as a plain dependency, not `harbor[daytona]` or `harbor[cloud]`. Harbor's daytona environment class raises `MissingExtraError` at constructor time when `daytona-sdk` isn't importable. - -### Repro - -```powershell -$env:DAYTONA_API_KEY = "..." -nasde run --variant foo --tasks bar -C bench --harbor-env daytona --without-eval -# → MissingExtraError: The 'daytona' package is required but not installed. -# Install it with: -# pip install 'harbor[daytona]' -# uv tool install 'harbor[daytona]' -``` - -### Impact - -Cross-platform issue, not Windows-specific — but on Windows it compounds with Issue 2 because Issue 2 hides Issue 3 behind a more confusing crash message. - -### Workaround - -```powershell -uv run --with 'harbor[daytona]' nasde run --harbor-env daytona … -``` - -Or for the global tool: -```powershell -uv tool install --reinstall --with 'harbor[daytona]' . -``` - -### Proposed fix - -Three options, in order of preference: - -1. **Add `harbor[cloud]` as a default dependency** — small package size impact, every user gets daytona/modal/e2b/runloop/gke out of the box. -2. **Document explicit `--with` syntax** in README and `--harbor-env` help text. -3. **Detect missing extra at CLI level** and print actionable message: - ``` - error: --harbor-env daytona requires the 'harbor[daytona]' extra. - install with: uv tool install --reinstall --with 'harbor[daytona]' . - ``` - -Recommendation: **(1)**. The current behavior trades ~30 MB of disk for a setup wall every cloud user hits. - ---- - -## Cross-cutting recommendations - -### Tier 1 — should land before announcing nasde for Windows - -- **Set `PYTHONUTF8=1` at CLI entrypoint** on Windows (fixes Issues 1, 2 with one line). - -### Tier 2 — quality of life - -- **Add `harbor[cloud]` to default deps** OR detect-and-actionable-error on missing extra (Issue 3). -- **Replace Unicode glyphs (`✓`, `•`) in user-facing print statements with ASCII fallbacks**, even with PYTHONUTF8 on, so error paths don't crash on font/console edge cases. - ---- - -## Verification environment for reproduction - -``` -OS: Windows 11 Pro 26100 -Shell: PowerShell 5.1 (powershell.exe, NOT pwsh 7+) -Locale: Polish, console code page cp1250 -Python: cpython 3.13.13 + 3.14.4 installed (uv-managed) -uv: 0.9.x -git: 2.x with core.autocrlf=true (default) -nasde: 0.3.3.dev5 (PR #45 head) -``` - -To reproduce on a clean machine, ensure these conditions match — especially the cp1250 locale, which is what surfaces Issues 1 and 2. On `en-US` Windows (cp1252), Issue 1's `✓` (U+2713) would *not* crash, because cp1252 also lacks U+2713 but Rich's path differs. The Braille spinner from Issue 2 fails on both cp1250 and cp1252 since neither covers U+2800.