MCP Servers
- ALWAYS use
cmd /c wrapper: "command": "cmd", "args": ["/c", "npx", ...]
- Never use bash syntax directly in MCP configs
Dev Server
preview_start first, with a .claude/launch.json entry. It owns the
server lifecycle, reuses an already-running server, and exposes the logs via
preview_logs. The browser skill holds the full loop.
- Nothing to preview, or no
launch.json? Then Bash({ command: "npm run dev", run_in_background: true }). A backgrounded Bash command is detached and
survives across turns.
- Never
start cmd /k. It opens a window no tool can read, so the server's own
error output becomes invisible — a failed compile looks identical to a slow one.
- Check the port first:
netstat -ano | findstr :3000
Superseded 2026-08-17. This section used to forbid npm run dev outright on the
grounds that it "gets killed on session end". That premise is now false twice
over: run_in_background detaches the process, and preview_start supervises it
outright. The rule was compensating for a limitation the harness no longer has.
Paths
- Use forward slashes in code:
src/lib/utils.ts
- Use backslashes only for Windows commands:
cd C:\Users\...
Environment Variables
- System env vars available to all processes
- Reference in .env.local:
${GOOGLE_CLIENT_ID} or leave it to system
- Check with:
echo %VARIABLE_NAME% (cmd) or $env:VARIABLE_NAME (PowerShell)
Supabase CLI
supabase db query --linked triggers Windows Firewall prompts and times out — never use it
- Use REST API instead — fully automatable, no firewall issues:
- Read:
curl.exe 'https://<ref>.supabase.co/rest/v1/<table>?select=*' -H 'apikey: <anon_key>' -H 'Authorization: Bearer <service_role_key>'
- RPC/SQL:
curl.exe -X POST 'https://<ref>.supabase.co/rest/v1/rpc/<fn>' -H 'apikey: <anon_key>' -d '{}'
- Schema:
curl.exe 'https://<ref>.supabase.co/rest/v1/' -H 'apikey: <anon_key>' (lists tables)
- Write
curl.exe, not curl — see the alias trap under Common Gotchas.
- Keys come from env vars (
SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY) or .env.local
Common Gotchas
- Always write
curl.exe, never bare curl. In Windows PowerShell 5.1
curl is an alias for Invoke-WebRequest, which does not understand -H,
-d or -X and fails with a parameter-binding error that never mentions
curl. Measured on this machine 2026-08-17: Get-Command curl returns
CommandType: Alias, Definition: Invoke-WebRequest. curl.exe bypasses the
alias and is the real binary in every shell.
curl.exe ships in C:\Windows\System32, so it is available in plain cmd
too. The old rule here claimed the opposite.
- Use
where instead of which for finding executables
- Line endings: ensure
.gitattributes has * text=auto
Writing PowerShell blocks in skills and docs
How you fence a block decides whether the Windows conventions are enforced on it.
check-superseded scans powershell, ps1 and pwsh fences for Windows
conventions and deliberately exempts bash, sh, zsh, shell and console
fences — even inside a file whose name matches windows. So the label is not
cosmetic: it is the switch that decides which rules apply.
Label every block, and label it honestly.
- An unlabelled
``` fence is scanned by nothing. If the content is PowerShell,
say powershell — otherwise the Windows rules silently do not apply to it.
- Never put bash inside a
powershell fence, or PowerShell inside a bash fence.
The Windows rules then fire on content they should spare, or spare content they
should catch. Two fences beat one mislabelled fence.
- A Mac/Linux comparison block belongs in its own
bash fence. That is explicitly
fine here and the detector will leave it alone.
Close what you open, and mind the marker length. Fence tracking follows the
CommonMark rule: a fence closes only on the same character, at least as long
as the opener, with no info string.
- An unclosed fence used to leak its language to the end of the file, so prose
twenty lines down was read as PowerShell. It no longer does, but an unclosed
fence still renders wrong for the reader.
- Inside a
````markdown wrapper, an inner ```powershell is content, not
an instruction — a displayed example. That is correct, and it also means you
cannot enforce a rule on a block you are only demonstrating.
~~~powershell is a valid fence and is scanned. Prefer backticks for
consistency, but tildes are the escape hatch when the block itself contains
backtick fences.
Content rules inside a PowerShell block, all of which the detector or the
gotchas above cover:
| Write |
Instead of |
Because |
curl.exe with flags |
bare curl |
curl is an alias for Invoke-WebRequest in PS 5.1 |
cmd1; cmd2 |
cmd1 && cmd2 |
&& is not a PS 5.1 operator |
| backtick continuation |
\ continuation |
\ is not a line continuation in PowerShell |
$env:VAR |
%VAR% / $VAR |
cmd and POSIX syntax respectively |
New-Item -Force |
mkdir -p |
no -p on the PowerShell alias |
Select-String |
grep |
not present unless Git Bash is on PATH |
Note what happened while that table was being written. The first draft spelled the
wrong form out in full, and check-superseded flagged its own documentation at
SKILL.md:96 — correctly, because a bare curl followed by a flag is an
instruction wherever it appears, including in a table cell labelled as wrong. A doc
that shows a banned form has to name it, not invoke it. bare curl carries
the meaning; curl plus a flag carries the bug.
One trap that is not about fences at all. When a script shells out with a git
ref, use execFileSync with an argv array, never execSync with a string.
execSync routes through cmd.exe /d /s /c, where ^ is the escape character, so
git rev-parse HEAD^ returns HEAD's own sha — silently, with exit 0. Measured
2026-08-17: execSync gave af3bd7b where execFileSync gave faa3c21. Any
caret-bearing ref (HEAD^, HEAD^^, main^, HEAD^2) is affected.
1---2name: rule-windows3description: "Windows-specific development rules: cmd /c wrappers for MCP, dev servers in an external terminal, path conventions, and the Supabase CLI firewall workaround. Load only when working on Windows."4when_to_use: "Background rules that apply only on Windows hosts. Not user-invocable."5user-invocable: false6allowed-tools: Read, Grep, Glob7paths:8 - "**/*.ps1"9---1011## MCP Servers12- ALWAYS use `cmd /c` wrapper: `"command": "cmd", "args": ["/c", "npx", ...]`13- Never use bash syntax directly in MCP configs1415## Dev Server16- **`preview_start` first**, with a `.claude/launch.json` entry. It owns the17 server lifecycle, reuses an already-running server, and exposes the logs via18 `preview_logs`. The `browser` skill holds the full loop.19- Nothing to preview, or no `launch.json`? Then `Bash({ command: "npm run dev",20 run_in_background: true })`. A backgrounded Bash command is detached and21 survives across turns.22- Never `start cmd /k`. It opens a window no tool can read, so the server's own23 error output becomes invisible — a failed compile looks identical to a slow one.24- Check the port first: `netstat -ano | findstr :3000`2526Superseded 2026-08-17. This section used to forbid `npm run dev` outright on the27grounds that it "gets killed on session end". That premise is now false twice28over: `run_in_background` detaches the process, and `preview_start` supervises it29outright. The rule was compensating for a limitation the harness no longer has.3031## Paths32- Use forward slashes in code: `src/lib/utils.ts`33- Use backslashes only for Windows commands: `cd C:\Users\...`3435## Environment Variables36- System env vars available to all processes37- Reference in .env.local: `${GOOGLE_CLIENT_ID}` or leave it to system38- Check with: `echo %VARIABLE_NAME%` (cmd) or `$env:VARIABLE_NAME` (PowerShell)3940## Supabase CLI41- `supabase db query --linked` triggers Windows Firewall prompts and times out — **never use it**42- **Use REST API instead** — fully automatable, no firewall issues:43 - Read: `curl.exe 'https://<ref>.supabase.co/rest/v1/<table>?select=*' -H 'apikey: <anon_key>' -H 'Authorization: Bearer <service_role_key>'`44 - RPC/SQL: `curl.exe -X POST 'https://<ref>.supabase.co/rest/v1/rpc/<fn>' -H 'apikey: <anon_key>' -d '{}'`45 - Schema: `curl.exe 'https://<ref>.supabase.co/rest/v1/' -H 'apikey: <anon_key>'` (lists tables)46 - Write `curl.exe`, not `curl` — see the alias trap under Common Gotchas.47- Keys come from env vars (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`) or `.env.local`4849## Common Gotchas50- **Always write `curl.exe`, never bare `curl`.** In Windows PowerShell 5.151 `curl` is an alias for `Invoke-WebRequest`, which does not understand `-H`,52 `-d` or `-X` and fails with a parameter-binding error that never mentions53 curl. Measured on this machine 2026-08-17: `Get-Command curl` returns54 `CommandType: Alias, Definition: Invoke-WebRequest`. `curl.exe` bypasses the55 alias and is the real binary in every shell.56- `curl.exe` ships in `C:\Windows\System32`, so it is available in plain cmd57 too. The old rule here claimed the opposite.58- Use `where` instead of `which` for finding executables59- Line endings: ensure `.gitattributes` has `* text=auto`6061## Writing PowerShell blocks in skills and docs6263How you fence a block decides whether the Windows conventions are enforced on it.64`check-superseded` scans `powershell`, `ps1` and `pwsh` fences for Windows65conventions and deliberately exempts `bash`, `sh`, `zsh`, `shell` and `console`66fences — even inside a file whose name matches `windows`. So the label is not67cosmetic: it is the switch that decides which rules apply.6869**Label every block, and label it honestly.**7071- An unlabelled ` ``` ` fence is scanned by nothing. If the content is PowerShell,72 say `powershell` — otherwise the Windows rules silently do not apply to it.73- Never put bash inside a `powershell` fence, or PowerShell inside a `bash` fence.74 The Windows rules then fire on content they should spare, or spare content they75 should catch. Two fences beat one mislabelled fence.76- A Mac/Linux comparison block belongs in its own `bash` fence. That is explicitly77 fine here and the detector will leave it alone.7879**Close what you open, and mind the marker length.** Fence tracking follows the80CommonMark rule: a fence closes only on the *same* character, *at least as long*81as the opener, with no info string.8283- An unclosed fence used to leak its language to the end of the file, so prose84 twenty lines down was read as PowerShell. It no longer does, but an unclosed85 fence still renders wrong for the reader.86- Inside a ` ````markdown ` wrapper, an inner ` ```powershell ` is **content**, not87 an instruction — a displayed example. That is correct, and it also means you88 cannot enforce a rule on a block you are only demonstrating.89- `~~~powershell` is a valid fence and is scanned. Prefer backticks for90 consistency, but tildes are the escape hatch when the block itself contains91 backtick fences.9293**Content rules inside a PowerShell block**, all of which the detector or the94gotchas above cover:9596| Write | Instead of | Because |97|---|---|---|98| `curl.exe` with flags | bare `curl` | `curl` is an alias for `Invoke-WebRequest` in PS 5.1 |99| `cmd1; cmd2` | `cmd1 && cmd2` | `&&` is not a PS 5.1 operator |100| backtick continuation | `\` continuation | `\` is not a line continuation in PowerShell |101| `$env:VAR` | `%VAR%` / `$VAR` | cmd and POSIX syntax respectively |102| `New-Item -Force` | `mkdir -p` | no `-p` on the PowerShell alias |103| `Select-String` | `grep` | not present unless Git Bash is on PATH |104105Note what happened while that table was being written. The first draft spelled the106wrong form out in full, and `check-superseded` flagged its own documentation at107`SKILL.md:96` — correctly, because a bare `curl` followed by a flag is an108instruction wherever it appears, including in a table cell labelled as wrong. A doc109that shows a banned form has to **name** it, not **invoke** it. `bare curl` carries110the meaning; `curl` plus a flag carries the bug.111112**One trap that is not about fences at all.** When a script shells out with a git113ref, use `execFileSync` with an argv array, never `execSync` with a string.114`execSync` routes through `cmd.exe /d /s /c`, where `^` is the escape character, so115`git rev-parse HEAD^` returns HEAD's own sha — silently, with exit 0. Measured1162026-08-17: `execSync` gave `af3bd7b` where `execFileSync` gave `faa3c21`. Any117caret-bearing ref (`HEAD^`, `HEAD^^`, `main^`, `HEAD^2`) is affected.