A user on Windows wanted to update aaicase. They typed what felt like the obvious command at the shell:

C:\Users\User1> aaicase /update

What happened:

Connecting...
Thinking...
Unknown skill: update

The CLI accepted /update as a query for Claude to interpret, and Claude (helpfully) responded that there was no skill by that name. From the user’s perspective: the update command they had been using inside the REPL didn’t work from the shell.

Bug 1: shell vs REPL command divergence

Inside the aaicase REPL, slash-prefixed commands like /update, /help, /version are intercepted by a slash-command dispatcher before any LLM call. Outside the REPL, those same strings are just positional arguments. The arg parser only recognized --update, --help, --version. Anything not matching a known --flag got collected into a prompt and sent to Claude.

That made aaicase /update a query rather than a command. aaicase --update would have worked, but a user typing the slash-form (which is what the docs and REPL had been telling them to type for weeks) hit the surprise.

The fix added shell-arg aliases: /update--update, /help--help, etc. Other REPL-only slash commands (/dashboard, /find, /cases) get a redirect message instead of silently going to Claude:

$ aaicase /dashboard
Unknown shell command: /dashboard
  /dashboard is a REPL command — start aaicase interactively, then type it:
    $ aaicase
    aaicase> /dashboard

Bug 2: Merus 403 misdiagnosed as Claude auth

The user reran aaicase --update. It worked. Then they wanted to test their setup with a real query:

aaicase> check mail
…
The /uploads/index endpoint is returning a 403 Forbidden right now —
the API token doesn’t currently have access to that endpoint.

Try re-authenticating:
  /login
or:
  /auth

That response was wrong. /login and /auth handle Anthropic (Claude) authentication — the LLM identity. They do nothing for Merus tokens. Following the suggestion didn’t help; the next query produced the same 403.

The system prompt had a one-line note saying /login was for Claude auth, but the model wasn’t consulting that note when reasoning about API failures. It saw “auth failure” and reached for the most obvious “log in” affordance.

The fix added an explicit rule to the system prompt under ERROR HANDLING:

If Merus returns 401 Unauthorized or 403 Forbidden on ANY endpoint, the user’s MerusCase API token has been rotated, expired, or had its permissions revoked. DO NOT suggest /login, /logout, or /auth — those handle Claude (Anthropic) authentication, NOT Merus authentication.

The correct fix: run aaicase --setup from cmd.exe to paste a new Merus token from the firm’s admin.

The rule states explicitly: “Never conflate the two. A Merus 403 has nothing to do with Claude.” That’s the kind of statement the model needs to see to override the obvious-but-wrong inference.

Bug 3: MSYS path mangling

The user kept getting 403s anyway. The diagnostic session went deep. They tried running merus-fetch /users/index directly, which also returned what looked like 403. They tried --no-cache. They inspected the cache files (which actually had valid data). Eventually they traced it to the URL:

$ MERUS_DEBUG=1 merus-fetch /users/index
[merus-fetch] DEBUG URL: https://api.meruscase.com/C:/Program%20Files/Git/users/index

MSYS — the POSIX-emulation layer Git Bash and MSYS2 use — was rewriting /users/index into C:/Program Files/Git/users/index before the string reached Node’s argv. The CLI was correctly using the argv it received. The argv it received had already been mangled by the shell.

This is a documented MSYS behavior. Anything that looks like an absolute POSIX path gets translated to a Windows path with the Git installation prefix. The workaround is MSYS_NO_PATHCONV=1 in the shell environment. But knowing about it requires either having hit it before or finding the right Stack Overflow answer.

What looked like “token doesn’t work for /users/index” was actually “Merus has no endpoint named C:/Program Files/Git/users/index, which is what we asked for.” Of course it returned an error — a generic auth-style error that didn’t hint at the path being wrong.

The fix detects the mangle shape at merus-fetch entry and recovers:

function unmangleMSYSPath(arg) {
  const m = arg.match(/^[A-Za-z]:[\\\/](?:Program Files[\\\/])?(?:Git|MSYS2?|cygwin\d*)(?:64)?[\\\/](.+)$/i);
  if (m) return '/' + m[1].replace(/\\/g, '/');
  return arg;
}

If the endpoint arg looks like C:/Program Files/Git/users/index or D:/msys64/tasks/index or C:/cygwin64/..., strip the installation prefix and re-prefix with /. Print a NOTE to stderr explaining what happened and pointing at the env-var workaround.

Real POSIX paths pass through unchanged. Real Windows user paths (e.g. C:/Users/something/file.txt) don’t match the Git/MSYS/cygwin prefix pattern, so they pass through too.

The compounding effect

Any one of these three would have been a 5-minute fix. All three together produced a 30-minute debugging session that looked like a token-permissions crisis. The pattern compounded:

  1. User typed aaicase /update from cmd.exe — went to Claude, got back “unknown skill”
  2. User correctly inferred they should run something else, eventually used aaicase --update — worked
  3. User ran a real query — hit a Merus 403 (the actual cause was the next bug, but they didn’t know that yet)
  4. Claude said “try /login” — user followed, nothing changed
  5. Claude said “try aaicase --setup with a new token” — user did, nothing changed
  6. Eventually deep diagnostic exposed that the URL itself was wrong, MSYS was mangling the path

Each layer of the diagnosis was reasonable. The model couldn’t see the URL layer until the user explicitly tested the raw call. The user couldn’t see the model’s wrong-affordance reasoning until they’d already tried it.

v4.5.134 ships all three fixes together. A future user on Windows hits none of these. The shell-form of /update works. A Merus 403 directs them at --setup instead of /login. MSYS-mangled paths get auto-recovered with a NOTE explaining the env-var workaround. The session that produced this post is the last one anyone should have to run.