Skip to content

test: fix the suite's own path handling on Windows - #178

Draft
misantiago17 wants to merge 2 commits into
ramarivera:mainfrom
misantiago17:fix/windows-test-suite
Draft

misantiago17 wants to merge 2 commits into
ramarivera:mainfrom
misantiago17:fix/windows-test-suite

Conversation

@misantiago17

@misantiago17 misantiago17 commented Sep 20, 2026

Copy link
Copy Markdown

Opening as a draft: I'd like a sanity check on the approach before I polish it, and I can't run the suite on Linux or macOS myself.

Commit 1 — the suite's own path handling

On a Windows checkout, bun test reports 34 failures. 21 of them are the tests' own path handling, not the code they cover.

Windows 11, bun 1.3.11
before:  441 pass, 34 fail
after:   462 pass, 13 fail

No production code, no new tests, nothing skipped. Same 475 tests before and after — 21 of them just stopped asking the wrong question.

Cause 1: worker scripts generated as source text — 12 tests

adapters/shared/file-storage.test.ts writes worker scripts to disk and interpolates paths straight into string literals:

`import { FileBuddyStorage } from "${join(import.meta.dir, "file-storage.ts")}";`
`const stateDir = "${stateDir}";`

On Windows the path is full of backslashes, which are escape sequences once inside a string literal. The generated worker never parses:

error: Cannot find package 'C:Usersile-storage.ts'

\U, \T and \c eaten; \f in file-storage became a formfeed, leaving ile-storage. Every worker exits 1 and the nine locking tests fail with Worker exited with code 1.

Module paths now go through pathToFileURL(), data paths through JSON.stringify(). Both emit byte-identical output on POSIX.

Cause 2: POSIX-spelled literals as expectations — 4 tests

server/path.test.ts and cli/runtime-app.test.ts assert against "/tmp/profile/buddy-state" while the resolvers build with the platform separator, so Windows produces \tmp\profile\buddy-state. Both are correct — the literal just only matches on POSIX. The expectations now come from join(), which returns the very same strings there.

Cause 3: shell notation vs Node notation — 8 tests

server/paths_sh.test.ts compares paths.sh output against join(homedir(), ...). Under Git Bash the script answers /c/Users/dev/.claude; Node says C:\Users\dev\.claude — the same directory in two notations. Both sides are normalised to the shell's notation before comparing. Identity on POSIX.

Commit 2 — unrelated one-line cleanup

Not a Windows issue; I hit it while reading state.ts and it was five lines, so I tacked it on. Happy to pull it into its own PR — just say so.

buddy-comment.ts writes .last_stop_hook.<sid> once per Stop event, but the prefix is missing from both cleanup lists (TRANSIENT_PREFIXES in server/state.ts, and the pattern list in cli/uninstall.ts). Its siblings .last_comment.<sid> and reaction.<sid>.json are both listed, so this reads as an oversight. Every session leaves one behind permanently, and claude plugin uninstall walks past them.

The sweep in statusline/buddy-status.sh has the same gap — it expires reaction.* and .last_comment.* on reactionTTL and leaves .last_stop_hook.* alone. Deliberately not touched here: #177 is rewriting that script and this does not need to collide with it.

The 13 failures I deliberately left

Not a platform quirk, and not something to skip. The statusline genuinely renders a broken card on Windows — the right-hand column collapses to one character per row:

expected                              actual
   .------------------------.            .------------------------.
   | even the dead approve. |            | even the dead approve.
   `------------------------'            `------------------------'
                     .----.
                    / °  ° \                                      M
                    |      |
                    ~`~``~`~
                    Marble ★ Lv4

Eleven of the twelve fail on that: the name and stars are missing, the bubble never closes, and auto density cannot tell full from minimal because all three render the same nothing. The twelfth (invalid BUDDY_STATUSLINE_COLS values...) is a 5000 ms timeout rather than a content mismatch.

So statusline/buddy-status.test.ts is doing its job, and skipIf(win32) would hide a live defect. I'd rather leave it red.

This is adjacent to #177, which measures the same script at 23-29 s on the author's Windows box. Worth noting my numbers differ a lot: bun test statusline/ here is 21 pass / 12 fail in 88 s on the unpatched script, against 1 pass / 32 fail reported there. And a single run of the script completes in ~3.4 s — it is not truncated by a timeout, it finishes and still draws wrong. Whatever #177 fixes, something looks like it will remain. Glad to take that to #177 as a second data point rather than opening a competing issue — tell me which you prefer.

The 14th failure, server/mcp-launcher.sh: exists, is executable, is a genuine platform quirk: Windows has no execute bit, so mode & 0o111 is 0 where the test wants 73. That one really is unfixable rather than unfixed — happy to guard it, or leave it.

What I'd like feedback on

  1. Is normalising inside the test (cause 3) the shape you want, or would you rather paths.sh answered in a canonical notation?
  2. Should commit 2 be its own PR?
  3. I have further Windows fixes queued — the plugin manifest points the six hooks and the MCP server at .sh files, which Windows cannot execute as a command, so a plugin install is completely inert there. Separate PR, or fold in?

Testing

  • bun test on Windows 11 / bun 1.3.11 — 34 failures down to 13
  • bun run typecheck clean
  • Not run on Linux or macOS. Every change is an identity on POSIX by construction, but CI is the real confirmation, which is part of why this is a draft.

🤖 Generated with Claude Code

21 of the 34 failures on a Windows checkout come from the tests, not from
the code they cover. Three separate causes:

* adapters/shared/file-storage.test.ts builds its worker scripts as source
  text and interpolates paths straight into string literals. On Windows
  every backslash is then an escape sequence, so the import specifier
  C:\Users\...\file-storage.ts reaches bun as C:Users...ile-storage.ts and
  the worker exits 1 before it runs:

      error: Cannot find package 'C:Usersile-storage.ts'

  Module paths now go through pathToFileURL(), data paths through
  JSON.stringify(). Both produce byte-identical output on POSIX.

* server/path.test.ts and cli/runtime-app.test.ts asserted against
  "/"-spelled literals while the resolvers build paths with the platform
  separator. The expectations now come from join(), which returns the very
  same strings on POSIX.

* server/paths_sh.test.ts compared paths.sh output (/c/Users/... under Git
  Bash) against join(homedir(), ...) (C:\Users\...) — the same directory in
  two notations. Both sides are normalised to the shell's notation before
  comparing; identity on POSIX.

No production code is touched and nothing is skipped on Windows. The 13
remaining failures are real: the bash statusline renders a broken card
there (sprite collapsed to one character, unclosed bubble, missing name).
That is a separate report, and these tests should keep failing until it is
fixed.

Windows 11, bun 1.3.11: 34 failures -> 13. Not yet run on Linux or macOS;
every change above is an identity there, but CI is the confirmation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Michelle <michelle.santiago10@gmail.com>
@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

buddy-comment.ts writes .last_stop_hook.<sid> once per Stop event, but the
prefix is missing from both cleanup lists — TRANSIENT_PREFIXES in
server/state.ts and the pattern list in cli/uninstall.ts. Every session
leaves one behind for good, and `claude plugin uninstall` walks past them,
so the state dir keeps growing with files nothing reads again.

Its siblings .last_comment.<sid> and reaction.<sid>.json are both listed,
so this looks like an oversight rather than an intent to keep them.

Note the sweep in statusline/buddy-status.sh has the same gap: it expires
reaction.* and .last_comment.* on reactionTTL and leaves .last_stop_hook.*
alone. That one is left out here on purpose — ramarivera#177 is currently rewriting
that script, and this fix does not need to collide with it.

Signed-off-by: Michelle <michelle.santiago10@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant