Bug: app.scopes.query() and app.scopes.request() are inconsistent on development stores
Summary
On development stores, App Bridge’s scopes.query() reports optional scopes as
“granted” before scopes.request() has ever been called. However, the app’s
offline access token does not actually include those scopes until
scopes.request() is explicitly called. This creates a silent mismatch between
what the client-side API reports and what the server-side token can actually do.
On non-development stores, this inconsistency does not occur — scopes.query()
correctly reports optional scopes as not granted until the merchant approves
them via scopes.request().
Expected behavior
scopes.query() should reflect the actual state of the offline access token. If
optional scopes have not been requested via scopes.request(), they should not
appear in the granted array — regardless of store type.
Alternatively, if development stores are intentionally auto-granting optional
scopes in scopes.query(), the offline access token should also include those
scopes automatically.
Actual behavior
- App is installed on a development store with optional scopes declared in the
app config - app.scopes.query() returns those optional scopes in the granted array
- App trusts this response and skips calling app.scopes.request()
- The offline access token does not include those scopes
- All server-side API calls using those scopes fail
Impact
Any app that uses scopes.query() to conditionally call scopes.request() — a
reasonable pattern for handling re-installs or merchants revisiting a
permissions flow — will silently break on development stores. The app appears to
have permissions but API calls fail, with no indication to the merchant or
developer that scopes were not actually granted to the token.
Workaround
Always call scopes.request() unconditionally, regardless of what scopes.query()
returns. On development stores this resolves immediately without a
merchant-facing prompt.