Skip to content

fix(cli): continue explicit --server after legacy health 404 - #424

Merged
shuv1337 merged 2 commits into
integration-v2from
cursor/legacy-health-probe-5e6a
Oct 5, 2026
Merged

shuv1337 merged 2 commits into
integration-v2from
cursor/legacy-health-probe-5e6a

Conversation

@shuv1337

@shuv1337 shuv1337 commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Intent

The captain approved resuming the issue backlog (2026-10-04): "triage the ~22 Cursor draft PRs first, then small batches", and later answered yes that shuvbro's triage review counts as the first review for these Cursor PRs. The read-only triage report is at ~/repos/shuvbro/data/triage-cursor-shuvcode/report.md; read the section for each PR you handle. Verdicts: #424 (fix(cli): continue explicit --server after legacy health 404, issue #399) is land: only a modern-server UnexpectedStatus 404 on /api/info falls back to probing /api/health then /api/status and continues with a warning; 401/503 still fail; 10 local tests pass. #428 (fix(cli): accept --standalone before subcommands, issue #416) is fix-then-land with an open review finding (#428 (comment)): globally accepted --server/--standalone are silently ignored by handlers that bypass ServerConnection.resolve - service stop/restart/start/set/unset, pair, acp, serve - so e.g. shuvcode --server http://other:4096 service stop stops the LOCAL service. Required: keep true global parsing for supported client commands but reject explicitly requested unsupported connection flags BEFORE any side effect on those paths (or implement real endpoint support where valid), update help with the support boundary, and add regressions for rejection, no local effects, and real endpoint/private selection across flag placements. Both PRs change packages/cli/src/services/server-connection.ts. The triage's land verdict preserves explicit endpoint selection without managed-service replacement, warns on modern version skew, legacy version skew, versionless health, or all-404, and falls back only on modern UnexpectedStatus 404. For the global flag repair, every local lifecycle/config/pair/serve/ACP case listed must reject unsupported explicitly requested connection flags before effects; ignoring flags on unrelated non-server commands is allowed. Existing tests only prove placements reach mutual exclusion; actual requested remote endpoint and standalone selection must be exercised.

What Changed

  • ServerConnection.resolve with an explicit --server now treats an UnexpectedStatus 404 from /api/info as a sign of an older server build. It probes /api/health and then /api/status, and continues with that endpoint instead of failing or replacing it with a managed service. Every other /api/info failure, such as 401 or 503, still fails as before.
  • On the legacy path the client always prints a warning to stderr. If a legacy endpoint reports a version, the warning shows that version and which path answered. Separate warnings cover a legacy response with no version and the case where every path returned 404. A modern /api/info version mismatch still prints the existing skew warning and skips the legacy probe.
  • Adds server-connection tests for: falling back to /api/health, falling back to /api/status, all paths returning 404, modern version skew with no legacy probe, legacy health with no version, and the unchanged 401 and 503 failures.

Risk Assessment

✅ Low: The change is small. Only an UnexpectedStatus 404 from /api/info enters the legacy fallback, while 401 and 503 still fail. The explicit endpoint is kept without managed-service replacement, and behavior-level tests cover each warning path. This matches the triage 'land' verdict for PR #424.

Testing

I installed dependencies and ran the real shuvcode CLI (models --server) against a local stub server in seven modes, using isolated XDG dirs. The 404 fallback paths (legacy /api/health version, /api/status version, versionless health, all-404) each print the right warning, send the follow-up /api/model request to the explicit endpoint, and list the model with exit 0. A modern server with a different version warns and does not probe the legacy routes. 401 and 503 still fail with the existing errors. An A/B run with the base resolver shows the old behaviour: legacy-404 and all-404 exited 1. The focused server-connection test file also passes. The evidence is the CLI transcripts and the stub/driver scripts in the evidence directory. There is no UI surface, so no screenshots.

  • Live validation: ✅ go - 9 of 9 scenarios driven live against the product
Scenario Result Live Evidence
User runs shuvcode models --server <legacy-url> where /api/info is 404 and /api/health reports a version: the CLI warns about the legacy version and completes against that server ✅ pass live cli-transcript.txt MODE=legacy-health: exit 0, model listed, warning 'version 0.0.0-legacy via /api/health', requests /api/info, /api/health, /api/model
Legacy server that only answers /api/status: the CLI probes /api/health, then /api/status, warns, and completes ✅ pass live cli-transcript.txt MODE=legacy-status: exit 0, warning 'via /api/status'
Legacy health answers without a version: the CLI warns about the versionless response and completes ✅ pass live cli-transcript.txt MODE=versionless: exit 0, warning 'answered /api/health without a version'
All of /api/info, /api/health and /api/status return 404: the CLI prints an actionable warning and still continues against the explicit server ✅ pass live cli-transcript.txt MODE=all404: exit 0, warning 'Restart or upgrade that server ... Continuing anyway'
Modern server with a different version: the CLI warns about the version skew and never probes the legacy routes ✅ pass live cli-transcript.txt MODE=modern-skew: exit 0, warning 'version 9.9.9-other', requests only /api/info and /api/model
Guard: a 401 on /api/info still fails with the password error and makes no fallback requests ✅ pass live cli-transcript.txt MODE=unauthorized: exit 1, 'requires a password; set OPENCODE_PASSWORD', only GET /api/info
Guard: a 503 on /api/info still fails and makes no fallback requests ✅ pass live cli-transcript.txt MODE=unavailable: exit 1, 'did not provide a compatible V2 health response', only GET /api/info
An explicit --server never starts or replaces a managed background service ✅ pass live cli-transcript.txt: in every mode, all follow-up requests went to the stub URL; the isolated state dirs contain no service artifacts, only a log
Regression reproduction: with the base resolver, the legacy-404 and all-404 cases fail ✅ pass live cli-transcript-baseline.txt: both modes exit 1 with 'UnexpectedStatus: 404' and never reach /api/health
Evidence: CLI transcript: shuvcode models --server against 7 stub modes (HEAD)
=== MODE=legacy-health  $ shuvcode models --server http://localhost:41621/
--- exit: 0
--- stdout:
stubprov/stub-model
--- stderr:
Warning: Server at http://localhost:41621/ has version 0.0.0-legacy via /api/health; this client is local. Continuing anyway.
--- requests received by stub:
GET /api/info
GET /api/health
GET /api/model

=== MODE=legacy-status  $ shuvcode models --server http://localhost:40109/
--- exit: 0
--- stdout:
stubprov/stub-model
--- stderr:
Warning: Server at http://localhost:40109/ has version 0.0.0-status via /api/status; this client is local. Continuing anyway.
--- requests received by stub:
GET /api/info
GET /api/health
GET /api/status
GET /api/model

=== MODE=versionless  $ shuvcode models --server http://localhost:42373/
--- exit: 0
--- stdout:
stubprov/stub-model
--- stderr:
Warning: Server at http://localhost:42373/ answered /api/health without a version, and /api/info returned HTTP 404. This client is local. Continuing anyway.
--- requests received by stub:
GET /api/info
GET /api/health
GET /api/status
GET /api/model

=== MODE=all404  $ shuvcode models --server http://localhost:34093/
--- exit: 0
--- stdout:
stubprov/stub-model
--- stderr:
Warning: Server at http://localhost:34093/ returned HTTP 404 for /api/info and has no legacy /api/health or /api/status response. This client is local. Continuing anyway. Restart or upgrade that server if this client cannot connect.
--- requests received by stub:
GET /api/info
GET /api/health
GET /api/status
GET /api/model

=== MODE=modern-skew  $ shuvcode models --server http://localhost:34311/
--- exit: 0
--- stdout:
stubprov/stub-model
--- stderr:
Warning: Server at http://localhost:34311/ has version 9.9.9-other; this client is local. Continuing anyway.
--- requests received by stub:
GET /api/info
GET /api/model

=== MODE=unauthorized  $ shuvcode models --server http://localhost:45571/
--- exit: 1
--- stdout:
--- stderr:
Error: Server at http://localhost:45571/ requires a password; set OPENCODE_PASSWORD
    at <anonymous> (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/services/server-connection.ts:47:68)
    at cli.server-connection.resolve (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/commands/handlers/models.ts:12:44)
    at cli.server-connection.resolve (definition) (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/services/server-connection.ts:21:31)
    at cli.models (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/framework/runtime.ts:100:34)
    at cli.models (definition) (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/commands/handlers/models.ts:11:10) {
  [cause]: UnauthorizedError: Unauthorized
      at declared (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/client/src/promise/generated/client.ts:2259:35)
      at <anonymous> (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/client/src/promise/generated/client.ts:340:13)
      at processTicksAndRejections (native)
}
--- requests received by stub:
GET /api/info

=== MODE=unavailable  $ shuvcode models --server http://localhost:42749/
--- exit: 1
--- stdout:
--- stderr:
Error: Server at http://localhost:42749/ did not provide a compatible V2 health response
    at <anonymous> (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/services/server-connection.ts:47:68)
    at cli.server-connection.resolve (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/commands/handlers/models.ts:12:44)
    at cli.server-connection.resolve (definition) (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/services/server-connection.ts:21:31)
    at cli.models (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/framework/runtime.ts:100:34)
    at cli.models (definition) (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/commands/handlers/models.ts:11:10) {
  [cause]: ClientError: UnexpectedStatus: 503
      at <anonymous> (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/client/src/promise/generated/client.ts:344:11)
      at processTicksAndRejections (native) {
    [cause]: Error: {"status":503}
  }
}
--- requests received by stub:
GET /api/info

=== isolated state dirs after run (no managed service started/replaced):
/tmp/tmp.GrbBba0BDc/state
/tmp/tmp.GrbBba0BDc/state/shuvcode
/tmp/tmp.GrbBba0BDc/data
/tmp/tmp.GrbBba0BDc/data/shuvcode
/tmp/tmp.GrbBba0BDc/data/shuvcode/repos
/tmp/tmp.GrbBba0BDc/data/shuvcode/log
/tmp/tmp.GrbBba0BDc/data/shuvcode/log/opencode-local.log
Evidence: Baseline CLI transcript (base commit resolver) showing the legacy 404 failure
\### BASELINE (base commit 97f9afcadf server-connection.ts)
=== MODE=legacy-health  $ shuvcode models --server http://localhost:37329/
--- exit: 1
--- stdout:
--- stderr:
Error: Server at http://localhost:37329/ did not provide a compatible V2 health response
    at ~effect/Utils/internal (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/node_modules/.bun/effect@4.0.0-rc.112/node_modules/effect/dist/Utils.js:85:16)
    at failWithCatch (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/node_modules/.bun/effect@4.0.0-rc.112/node_modules/effect/dist/internal/effect.js:751:21)
    at processTicksAndRejections (native)
    at cli.server-connection.resolve (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/commands/handlers/models.ts:12:44)
    at cli.server-connection.resolve (definition) (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/services/server-connection.ts:21:31)
    at cli.models (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/framework/runtime.ts:100:34)
    at cli.models (definition) (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/commands/handlers/models.ts:11:10) {
  [cause]: ClientError: UnexpectedStatus: 404
      at <anonymous> (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/client/src/promise/generated/client.ts:344:11)
      at processTicksAndRejections (native) {
    [cause]: Error: {"status":404}
  }
}
--- requests received by stub:
GET /api/info

=== MODE=all404  $ shuvcode models --server http://localhost:37459/
--- exit: 1
--- stdout:
--- stderr:
Error: Server at http://localhost:37459/ did not provide a compatible V2 health response
    at ~effect/Utils/internal (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/node_modules/.bun/effect@4.0.0-rc.112/node_modules/effect/dist/Utils.js:85:16)
    at failWithCatch (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/node_modules/.bun/effect@4.0.0-rc.112/node_modules/effect/dist/internal/effect.js:751:21)
    at processTicksAndRejections (native)
    at cli.server-connection.resolve (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/commands/handlers/models.ts:12:44)
    at cli.server-connection.resolve (definition) (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/services/server-connection.ts:21:31)
    at cli.models (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/framework/runtime.ts:100:34)
    at cli.models (definition) (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli/src/commands/handlers/models.ts:11:10) {
  [cause]: ClientError: UnexpectedStatus: 404
      at <anonymous> (~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/client/src/promise/generated/client.ts:344:11)
      at processTicksAndRejections (native) {
    [cause]: Error: {"status":404}
  }
}
--- requests received by stub:
GET /api/info

=== isolated state dirs after run (no managed service started/replaced):
/tmp/tmp.DrW9RfVCQk/state
/tmp/tmp.DrW9RfVCQk/state/shuvcode
/tmp/tmp.DrW9RfVCQk/data
/tmp/tmp.DrW9RfVCQk/data/shuvcode
/tmp/tmp.DrW9RfVCQk/data/shuvcode/repos
/tmp/tmp.DrW9RfVCQk/data/shuvcode/log
/tmp/tmp.DrW9RfVCQk/data/shuvcode/log/opencode-local.log
Evidence: Stub server used for the remote endpoint
// Stub "remote" server for driving `shuvcode models --server <url>`.
// MODE: legacy-health | legacy-status | versionless | all404 | modern-skew | unauthorized | unavailable
const mode = process.env.MODE!
const log = process.env.LOG!
const models = { data: [{ id: "stub-model", providerID: "stubprov" }] }
const server = Bun.serve({
  port: 0,
  async fetch(request) {
    const url = new URL(request.url)
    await Bun.write(log, (await Bun.file(log).exists() ? await Bun.file(log).text() : "") + `${request.method} ${url.pathname}\n`)
    if (mode === "unauthorized") return Response.json({ _tag: "UnauthorizedError", message: "Unauthorized" }, { status: 401 })
    if (mode === "unavailable") return new Response("Unavailable", { status: 503 })
    if (url.pathname === "/api/model") return Response.json(models)
    if (mode === "modern-skew" && url.pathname === "/api/info")
      return Response.json({ version: "9.9.9-other", pid: 1, urls: [], paths: { tmp: "/tmp" } })
    if (mode === "legacy-health" && url.pathname === "/api/health") return Response.json({ version: "0.0.0-legacy" })
    if (mode === "legacy-status" && url.pathname === "/api/status") return Response.json({ version: "0.0.0-status", pid: 1 })
    if (mode === "versionless" && url.pathname === "/api/health") return Response.json({ healthy: true })
    return new Response("missing", { status: 404 })
  },
})
console.log(server.url.toString())
Evidence: Driver script
#!/usr/bin/env bash
# Drives the real shuvcode CLI (`bun run src/index.ts models --server <url>`) against each stub mode.
set -u
EV=~/.no-mistakes/evidence/01M44YEWVFM088AZJMKTEWM00E
CLI=~/.no-mistakes/worktrees/284027b67ce8/01M44YEWVFM088AZJMKTEWM00E/packages/cli
ISO=$(mktemp -d)
export XDG_CONFIG_HOME=$ISO/config XDG_DATA_HOME=$ISO/data XDG_STATE_HOME=$ISO/state XDG_CACHE_HOME=$ISO/cache HOME=$ISO/home
mkdir -p $HOME
for MODE in legacy-health legacy-status versionless all404 modern-skew unauthorized unavailable; do
  LOG=$ISO/$MODE.log; : > $LOG
  MODE=$MODE LOG=$LOG bun $EV/stub-server.ts > $ISO/url & PID=$!
  for i in $(seq 50); do [ -s $ISO/url ] && break; sleep 0.1; done
  URL=$(cat $ISO/url)
  echo "=== MODE=$MODE  \$ shuvcode models --server $URL"
  (cd $CLI && timeout 60 bun run src/index.ts models --server "$URL" 2>$ISO/err >$ISO/out); CODE=$?
  echo "--- exit: $CODE"; echo "--- stdout:"; cat $ISO/out; echo "--- stderr:"; cat $ISO/err
  echo "--- requests received by stub:"; cat $LOG
  kill $PID; wait $PID 2>/dev/null; : > $ISO/url
  echo
done
echo "=== isolated state dirs after run (no managed service started/replaced):"
find $ISO/state $ISO/data -maxdepth 3 2>/dev/null | head -20
ls /tmp | grep -c . >/dev/null
rm -rf $ISO
Evidence: Legacy health fallback excerpt
$ shuvcode models --server http://localhost:41621/
exit: 0
stdout: stubprov/stub-model
stderr: Warning: Server at http://localhost:41621/ has version 0.0.0-legacy via /api/health; this client is local. Continuing anyway.
requests: GET /api/info, GET /api/health, GET /api/model

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⏭️ **Rebase** - skipped

Step was skipped.

⚠️ **Review** - 1 info
  • ℹ️ packages/cli/src/services/server-connection.ts:106 - legacyHealth treats every non-OK legacy response the same way, including 401/403, network errors, and invalid JSON. Example: a legacy server returns 404 for /api/info and 401 for /api/health because the password is wrong. The command still continues, and the warning says the server "has no legacy /api/health or /api/status response". On the modern path a 401 fails immediately with a 'requires a password' message, so a credential problem here is mislabeled and only shows up later. Triage accepted continuing when every probe returns 404. Failing on a legacy 401, or naming the status in the warning, is optional polish.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 9 of 9 scenarios driven live against the product
Scenario Result Live Evidence
User runs shuvcode models --server &lt;legacy-url&gt; where /api/info is 404 and /api/health reports a version: the CLI warns about the legacy version and completes against that server ✅ pass live cli-transcript.txt MODE=legacy-health: exit 0, model listed, warning 'version 0.0.0-legacy via /api/health', requests /api/info, /api/health, /api/model
Legacy server that only answers /api/status: the CLI probes /api/health, then /api/status, warns, and completes ✅ pass live cli-transcript.txt MODE=legacy-status: exit 0, warning 'via /api/status'
Legacy health answers without a version: the CLI warns about the versionless response and completes ✅ pass live cli-transcript.txt MODE=versionless: exit 0, warning 'answered /api/health without a version'
All of /api/info, /api/health and /api/status return 404: the CLI prints an actionable warning and still continues against the explicit server ✅ pass live cli-transcript.txt MODE=all404: exit 0, warning 'Restart or upgrade that server ... Continuing anyway'
Modern server with a different version: the CLI warns about the version skew and never probes the legacy routes ✅ pass live cli-transcript.txt MODE=modern-skew: exit 0, warning 'version 9.9.9-other', requests only /api/info and /api/model
Guard: a 401 on /api/info still fails with the password error and makes no fallback requests ✅ pass live cli-transcript.txt MODE=unauthorized: exit 1, 'requires a password; set OPENCODE_PASSWORD', only GET /api/info
Guard: a 503 on /api/info still fails and makes no fallback requests ✅ pass live cli-transcript.txt MODE=unavailable: exit 1, 'did not provide a compatible V2 health response', only GET /api/info
An explicit --server never starts or replaces a managed background service ✅ pass live cli-transcript.txt: in every mode, all follow-up requests went to the stub URL; the isolated state dirs contain no service artifacts, only a log
Regression reproduction: with the base resolver, the legacy-404 and all-404 cases fail ✅ pass live cli-transcript-baseline.txt: both modes exit 1 with 'UnexpectedStatus: 404' and never reach /api/health
  • bun install at the repo root (needed to run the CLI)
  • drive.sh: for each stub mode (legacy-health, legacy-status, versionless, all404, modern-skew, unauthorized, unavailable), ran bun run src/index.ts models --server &lt;stub-url&gt; in packages/cli with isolated XDG/HOME dirs and recorded exit code, stdout, stderr and the requests the stub received
  • A/B baseline: temporarily put back git show 97f9afcadf:packages/cli/src/services/server-connection.ts, ran the legacy-health and all404 modes again, then restored HEAD (git status clean)
  • bun test test/server-connection.test.ts in packages/cli (9 pass)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

cursoragent and others added 2 commits October 4, 2026 07:56
pc_db4667ac9ad5

An installed server that still answers /api/health or /api/status returns 404 for /api/info. Explicit --server now warns and continues instead of exiting.
@shuv1337
shuv1337 marked this pull request as ready for review October 5, 2026 02:43
@shuv1337
shuv1337 merged commit 16080e2 into integration-v2 Oct 5, 2026
6 checks passed
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