Skip to content

Add mapbox doctor: a read-only snapshot of the environment - #40

Open
mattpodwysocki wants to merge 10 commits into
mainfrom
feat/178-doctor-command
Open

mattpodwysocki wants to merge 10 commits into
mainfrom
feat/178-doctor-command

Conversation

@mattpodwysocki

Copy link
Copy Markdown
Contributor

Summary

Stacked on #39 (mapbox config list/unset), which is itself stacked on #35 (mapbox config get/set). Base branch is feat/177-config-list-unset, so this diff is just the incremental change — rebase onto main once #35 and #39 merge.

  • mapbox doctor — a read-only snapshot of what the next command would see: which token wins and its state, which proxy variables are in effect, and where the update-check/telemetry switches currently stand.
  • --verify additionally checks that api.mapbox.com is reachable, through the same http::client() (and therefore the same proxy handling) every other request in this crate uses. Nothing is sent otherwise — same precedent auth whoami --verify already sets.
  • Reuses auth::resolve_source for the token report rather than re-deriving the precedence. token_expires_at/token_usage moved from private to pub(crate) for this; token_account/load_credentials were already public.
  • MAPBOX_INTERNAL_DOCTOR_URL overrides the checked host — undocumented, for the same reason update_check.rs's MAPBOX_INTERNAL_UPDATE_URL exists: without a seam, --verify could only ever be tested against the real network.

Test plan

  • cargo build, cargo fmt, cargo clippy --all-targets -- -D warnings, cargo test — all clean
  • New tests/doctor.rs: bare state, a token in the environment, both switches (persisted + env), a proxy variable, and — through a loopback server — both the reachable and unreachable --verify outcomes, plus a timing assertion that no request happens at all without --verify
  • docs/commands.md and CHANGELOG updated
  • Manually verified against the real api.mapbox.com too (not just the loopback server)

auth whoami already answers which token the next command will use; this
answers the rest of what commonly goes wrong before a real command finds
out the hard way: a proxy variable silently not doing what someone thinks,
or the update-check/telemetry switches resolving to something other than
what was intended.

Reuses auth::resolve_source for the token report rather than re-deriving
the precedence — that function is the one place the order is decided, and
auth::whoami's own docs explain why a second opinion here would be worse
than none. token_expires_at and token_usage move from private to
pub(crate) for this; token_account and load_credentials were already
public.

--verify additionally checks that api.mapbox.com is reachable, through
the same http::client() (and therefore the same proxy handling) every
other request in this crate uses. Nothing is sent otherwise — the same
precedent auth whoami --verify already sets: a diagnostic command should
not itself be the request that reveals the problem it exists to describe.

MAPBOX_INTERNAL_DOCTOR_URL overrides the checked host, for the same reason
update_check.rs's MAPBOX_INTERNAL_UPDATE_URL exists: without a seam,
--verify could only ever be tested against the real network.
tests/doctor.rs drives both the reachable and unreachable cases through a
loopback server, and confirms nothing is sent at all when --verify is
absent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mattpodwysocki
mattpodwysocki requested a review from a team as a code owner September 23, 2026 03:48
@zmofei
zmofei added this pull request to stack #41 September 23, 2026 11:49
@zmofei
zmofei removed this pull request from stack #41 September 23, 2026 11:58

@zmofei zmofei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few correctness bugs in doctor's diagnostics, plus one design question and one cleanup suggestion.

Comment thread src/doctor.rs Outdated
Comment thread src/doctor.rs Outdated
Comment thread src/doctor.rs Outdated
Comment thread src/doctor.rs Outdated
Comment thread src/doctor.rs Outdated
mattpodwysocki and others added 3 commits September 23, 2026 09:17
Bugs:
- PROXY_VARS now lists both cases (HTTPS_PROXY and https_proxy, etc.) —
  reqwest reads both, curl's own convention, and the old list only had
  the uppercase spelling. Documented the remaining gap against the true
  question rather than closing it silently: this still answers "is a
  variable set", not "would this request use one" (NO_PROXY can exempt
  a host, a scheme-specific variable only applies to that scheme), since
  reqwest has no public API to ask "was a proxy applied" directly.
- The reported update-check state now depends on telemetry_allowed too,
  not just the persisted setting and the dedicated env switch.
  MAPBOX_CLI_NO_TELEMETRY silences the update check as well (see
  update_check.rs's module docs), which the first version of this line
  forgot, so MAPBOX_CLI_NO_TELEMETRY=1 alone printed "Update check: on"
  for a check that would not run.
- --verify now threads http::requested(matches) through instead of a
  hardcoded 5-second budget, so --timeout/MAPBOX_TIMEOUT affect it like
  any other request. Falls back to 5 seconds — a diagnostic someone is
  waiting on, not a request a command line's payload bounds — only when
  neither was given.
- The connectivity JSON's error field is now omitted outside --debug via
  skip_serializing_if, rather than present and set to null.

Structural fix requested in review: lines and JSON used to be built
independently from the same underlying values, which is exactly how the
telemetry bug above happened — the JSON read three raw switches while the
text line's own condition asked only two of them. Report and its four
constituent structs are now the one source both the text and the JSON
renderer read from, so the same drift can't happen twice.

tests/doctor.rs covers all four bugs: a lowercase proxy variable, telemetry
alone turning the reported update check off, an explicit --timeout cutting
a hung connection short, and the error field's presence keyed to --debug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mattpodwysocki

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. Addressed everything:

  • Proxy vars: PROXY_VARS now lists both cases (HTTPS_PROXY/https_proxy, etc.) — reqwest reads both, curl's convention. On the design question: kept the env-var list rather than asking the client to expose "was a proxy applied" — reqwest has no public API for that, so closing the remaining gap (NO_PROXY exempting a specific host, a scheme-specific variable only applying to that scheme) would mean asking upstream, not something to build around here. Documented the gap explicitly in both the code comment and the docs page instead of implying more precision than this has.
  • Update-check line: now depends on telemetry_allowed too. MAPBOX_CLI_NO_TELEMETRY=1 alone now correctly reports "Update check: off".
  • --timeout: --verify now threads http::requested(matches) through, falling back to 5s only when neither --timeout nor MAPBOX_TIMEOUT was given.
  • error field: now omitted entirely outside --debug via skip_serializing_if, not set to null.
  • Structural fix: replaced the two independently-built Value trees with one Report struct (and four typed sub-structs) that both the text and JSON renderers read from — the same shape auth::Identity uses. This is exactly what let the telemetry bug happen: the JSON read three raw switches, the text line's own condition asked only two.

New tests for all four bugs in tests/doctor.rs: a lowercase proxy variable, telemetry alone flipping the reported update-check state, an explicit --timeout cutting a hung connection short (via a loopback server that accepts but never answers), and the error field's presence keyed to --debug.

mattpodwysocki and others added 3 commits September 23, 2026 09:20
Windows environment variables are case-insensitive, so HTTPS_PROXY and
https_proxy are the same variable there. Checking both spellings as
independent PROXY_VARS entries reported a single value set once under
both names on Windows CI — caught by the platform's own test run, not
locally on macOS where the two are genuinely distinct.

PROXY_VARS is now four (uppercase, lowercase) pairs; either being set
reports one entry, always spelled with the uppercase name. This also
answers a cleaner question than four-to-eight independent checks did:
"is a proxy configured for this concern" rather than "list every
spelling that happens to be set", which nobody was asking for anyway.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Verified against hyper-util's own source rather than assumed from curl's
reputation: get_first_env(&["HTTPS_PROXY", "https_proxy"]) checks exactly
those two spellings and nothing else, so a mixed-case Https_Proxy is not
a gap in this list — it would not be honored by the client either, on a
case-sensitive OS.

The real fourth gap, worth naming: on macOS, hyper-util falls back to the
system's Dynamic Store proxy settings when no env var is set at all, and
this reads only the environment, so a proxy configured solely through
Network settings is invisible to `mapbox doctor` even though reqwest
would still use it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mattpodwysocki
mattpodwysocki changed the base branch from feat/177-config-list-unset to main September 23, 2026 14:21
@mattpodwysocki

Copy link
Copy Markdown
Contributor Author

Closing and reopening to force a CI run — the base-branch retarget didn't trigger one and neither did two follow-up pushes.

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