Skip to content

Give missing-scope errors the right next step - #182

Merged
tmfahey merged 2 commits into
mainfrom
missing-scope-next-step
Oct 22, 2026
Merged

tmfahey merged 2 commits into
mainfrom
missing-scope-next-step

Conversation

@tmfahey

@tmfahey tmfahey commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • A missing-scope 403 told every stored session to sign in again. It also passed along the API's "reach out to developer@gusto.com" text, which a CLI user can't act on. Signing in again doesn't help when the scope isn't part of the CLI's OAuth grant.
  • The insufficient_scope error now lists the scopes the API named in missing_scopes and the error shape in category. For a stored session, the message says which scopes signing in again can add. For any it can't add, it points to gusto feedback.
  • A scope counts as grantable when it's in REQUIRED_SCOPES or the new RETAINED_SCOPES. Tokens from GUSTO_ACCESS_TOKEN or --token-stdin keep the replace-the-token advice.
  • details now carries only unrelated errors from the same response. Every other 4xx and 5xx still gets the full body.
  • The second commit fixes an unrelated upgrade test that broke on the newer Ubuntu runner image. There, /bin/echo is a multi-call coreutils binary that won't run under the staging file's name, so the test now copies Bun's own binary.

Not in this PR:

  • MCP-backed commands (timesheet, feedback) still suggest re-login on a scope error.
  • A scope error nested inside another error entry could still carry the API's text in details. I haven't seen the API send that shape.
  • RFC 6750 scope lists come back sorted, while Gusto's own entries keep the API's order.

Linked issue

AINT-1118

Test plan

  • bun run test:all passes locally
  • Manual run of touched commands works against sandbox
  • --agent and --human output verified where touched

Against sandbox I ran the grantable, outside-the-grant, env-token, stdin-token and 404 cases, in JSON and human output. I also ran the built binary against a local stub through api request and payroll list for the edge cases: no scope named, mixed scopes, RFC scope lists, empty scope names, and plain 403/422/500. None of the output contained developer@gusto.com.

DCO

  • Every commit is signed off (git commit -s) per the DCO

A missing-scope 403 used to tell every stored session to sign in again
and passed through the API's "reach out to developer@" text, neither of
which helps when the scope isn't in the CLI's OAuth grant. The error now
names the scopes in missing_scopes and the API's shape in category, says
whether signing in again can add each scope, and points to gusto
feedback when it can't. Explicit tokens keep their replace-the-token
advice. details only carries unrelated errors from the same response.

Signed-off-by: tmfahey <taylor.fahey@gusto.com>
@tmfahey
tmfahey marked this pull request as ready for review October 22, 2026 16:39
@tmfahey
tmfahey requested review from a team and ashieh as code owners October 22, 2026 16:39
On newer Ubuntu runners /bin/echo is a link into the multi-call Rust
coreutils binary, which picks its command from argv[0]. Copied to the
upgrade's staging name it exits 1 with "unknown program", so the test
failed for a reason unrelated to the upgrade path it covers.

Signed-off-by: tmfahey <taylor.fahey@gusto.com>

@cbento-gusto cbento-gusto left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change gives a CLI user a scope error they can act on. The grantable and ungrantable split is clear, the API's partner-facing text no longer leaks through, and the tests cover the mixed, plural, unnamed, and explicit-token cases well.

Checked and clear:

  • Callers that read error.details (clarifyVersionConflict, clarifyEmptyTimesheetSync) gate on ExitCode.ApiClient, so the trimmed details on an Auth exit does not reach them.
  • Production ApiClients always carry auth, so the new case undefined grouping only changes test-only paths.
  • scopesFromBody dedupes Gusto entries, drops empty names, and falls back to the RFC 6750 scope in the same order scopeErrorCategory uses.
  • nonScopeDetails keeps unrelated errors, and human mode still prints them as reason:.

Blockers: None.

🤖 for @cbento-gusto

@tmfahey
tmfahey merged commit d72a7e9 into main Oct 22, 2026
14 checks passed
@tmfahey
tmfahey deleted the missing-scope-next-step branch October 22, 2026 18:50
@jeff-gusto jeff-gusto mentioned this pull request Oct 28, 2026
10 tasks done
jeff-gusto added a commit that referenced this pull request Oct 28, 2026
## Summary

- Bumps the root package version and public version examples from
`0.4.0` to `0.5.0`.
- Prepares `v0.5.0` for the missing-scope error changes (#182) and the
upgrade changes that keep `gusto upgrade` and auto-update working
through the repo's move between orgs (#185). The upgrade change only
reaches installed binaries once they update to a release that contains
it, so this should be released promptly.

## Linked issue

None.

## Test plan

- [x] `env -u NO_COLOR bun run test:all` passes locally
- [x] Manual run of touched commands works against sandbox — not
applicable; this changes only version metadata and documentation
- [x] `--agent` and `--human` output verified where touched — not
applicable; this changes no output behavior
- [x] `tsc --noEmit`
- [x] `bun run lint`
- [x] `bun run format:check`
- [x] `bun run license:check`
- [x] `bun run build`
- [x] Compiled binary reports `0.5.0`

## DCO

- [x] Every commit is signed off (`git commit -s`) per the
[DCO](https://github.com/Gusto/gusto-cli/blob/main/CONTRIBUTING.md#developer-certificate-of-origin)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Jeff Stephens <jeff.stephens@gusto.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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