Skip to content

fix(api): refuse a shell-mangled path instead of sending it as a 404 - #19

Merged
ArtemKosolap merged 1 commit into
mainfrom
dev/REPLY-51612-api-path-guard
Aug 4, 2026
Merged

fix(api): refuse a shell-mangled path instead of sending it as a 404#19
ArtemKosolap merged 1 commit into
mainfrom
dev/REPLY-51612-api-path-guard

Conversation

@ArtemKosolap

Copy link
Copy Markdown
Contributor

The bug

Under Git Bash / MSYS on Windows, reply api /v3/whoami never reached the CLI as typed. MSYS
converts a leading-slash argument into a Windows path, so the request went out as:

> GET https://api.reply.io/C:/Program Files/Git/v3/whoami     -> 404, exit 1

A 404 that reads exactly like an endpoint which does not exist. Quoting does not help — quotes
are removed before the conversion, which is the first thing anyone tries.

Why it earned a fix rather than a footnote

An agent exploring the product hit this on the prospect-search endpoints and reported to its user:

Reply's own prospect-discovery feature — Live Data search […] is documented but returns 404 on
this account
, so the beta isn't enabled for team 18185.

The feature works on that account; sibling runs in the same batch got 200. An environment quirk
became a confident, wrong statement about someone's own entitlements. This CLI is meant to be
driven by agents, and an agent cannot tell a mangled path from a missing endpoint when both produce
the same output.

One cause, not two

It was first reported as two findings — "any path without a query string returns a false 404" plus
"and separately, Git Bash rewrites the path". It is one cause. A ? stops MSYS treating the
argument as a path, so adding a dummy query string only appeared to fix it. Verified with
--verbose, which prints the URL actually requested:

invocation URL sent exit
reply api /v3/whoami (Git Bash) …/C:/Program Files/Git/v3/whoami 1
reply api "/v3/whoami" (Git Bash) …/C:/Program Files/Git/v3/whoami 1
reply api "/v3/whoami?_=1" …/v3/whoami?_=1 0
reply api //v3/whoami …/v3/whoami 0
MSYS_NO_PATHCONV=1 reply api /v3/whoami …/v3/whoami 0
reply api /v3/whoami (PowerShell) …/v3/whoami 0

Scope is narrower than first reported: Windows + Git Bash/MSYS only.

The fix

The path is validated before anything is spent — before auth, before a request. It must start with
/. Beyond that:

  • A drive-prefixed path is recognised as shell mangling rather than a typo.
  • The intended path is recovered from the mangled one, so the fix is quoted back verbatim
    instead of described: C:/Program Files/Git/v3/whoami yields reply api //v3/whoami. Both
    separators are handled, since MSYS emits forward slashes but a path pasted from cmd arrives with
    backslashes.
  • The error names MSYS, states that quoting will not help, and gives only remedies that were
    actually tested. Shipping an untested workaround in an error message would be the same class of
    defect as the bug itself.
  • Exit is 2 (usage), so a mangled path can never be confused with an upstream 404, which exits
    1.

A path without a leading slash that is not shell mangling gets plain guidance instead, with no
MSYS noise.

$ reply api /v3/whoami
The path must start with '/' — got 'C:/Program Files/Git/v3/whoami'.
  Hint: Your shell rewrote the argument before the CLI saw it: Git Bash / MSYS on
  Windows turns a leading-slash argument into a Windows path. Quoting does not
  help — quotes are removed before the conversion. Any of these does:
    reply api //v3/whoami
    MSYS_NO_PATHCONV=1 reply api /v3/whoami
    ...or run the same command from PowerShell or cmd.

Found on the way: 47 hints that nobody could see

The top-level handler printed only error.message and dropped hint entirely. Every hint on a
UsageError or RuntimeError was written and never shown — 47 call sites, including the one
that explains how to switch team, which this project treats as the canonical example of an
actionable error. Api_error was unaffected because it bakes its hint into the message, which is
why this went unnoticed.

Hints now print for all three, reusing Api_error's existing Hint: … shape so both kinds of
failure read the same. Continuation lines keep their own indentation, which is what makes a quoted
command stand out. --json is unchanged — the hint was already in to_json().

This is a behaviour change beyond the reported bug, and deliberately so: the fix's whole value is
an actionable message, and without it the new error would have printed one line and swallowed the
remedy.

Verification

  • Full suite green: 543 tests, 38 files. Nine new tests cover the guard, the recovery for any
    /vN/ segment, the non-mangled slashless case, the exit code, and format_hint including the
    multi-line shape.
  • One test asserts no request is made when the path is refused, so the guard cannot regress into
    spending a call.
  • Verified live from Git Bash against the built CLI: the broken invocation exits 2 with the hint;
    both recommended remedies return 200; a genuine 404 still exits 1.
  • No behaviour change on a path that was already correct, and none on PowerShell.

Out of scope

The 401 from an expired local CSM key noted in the same investigation — unrelated to the CLI.
OAuth through the CLI works.

Under Git Bash on Windows, `reply api /v3/whoami` never reached the CLI as
typed. MSYS converts a leading-slash argument into a Windows path, so the
request went to https://api.reply.io/C:/Program Files/Git/v3/whoami and came
back 404 — indistinguishable from an endpoint that does not exist. Quoting does
not help; quotes are removed before the conversion.

That cost real damage, not just a detour. An agent exploring the product hit it
on the prospect-search endpoints and told its user the feature "returns 404 on
this account, so the beta isn't enabled for team 18185". The feature works on
that account. An environment quirk became a confident, wrong statement about
someone's own entitlements.

One cause, not two. A dummy query string appeared to fix it only because a `?`
stops MSYS treating the argument as a path.

The path is now checked before anything is spent: it must start with `/`. A
drive-prefixed path is recognised as shell mangling, the intended path is
recovered from it so the fix can be quoted back verbatim rather than described,
and the error names MSYS, says that quoting will not help, and gives the two
remedies that do. Exit is 2 (usage), so a mangled path can never be mistaken
for an upstream 404, which exits 1. PowerShell, cmd, macOS and Linux see no
change.

Found while fixing it: the top-level handler printed only `error.message` and
dropped `hint` entirely, so every hint on a UsageError or RuntimeError was
written and never seen — 47 of them, including the one that explains how to
switch team. Api_error was unaffected because it bakes its hint into the
message. Hints now print for all three, in Api_error's existing `  Hint: ...`
shape, with continuation lines keeping their indentation so a quoted command
stands out. `--json` output is unchanged; the hint was already in `to_json`.
@ArtemKosolap
ArtemKosolap merged commit ab150f6 into main Aug 4, 2026
6 checks passed
@ArtemKosolap
ArtemKosolap deleted the dev/REPLY-51612-api-path-guard branch August 4, 2026 13:38
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.

2 participants