@@ -77,8 +77,11 @@ func (s *Server) handleBrowse(w http.ResponseWriter, r *http.Request) {
7777 }
7878 // No stale data — return a 503 with a clear outage message.
7979 msg := "Upstream database is temporarily unavailable — please try again in a moment."
80+ if isTokenPermissionError (err ) {
81+ msg = "DoltHub API token lacks SQL permissions — check token configuration."
82+ }
8083 slog .Error ("browse failed with no stale data" , "error" , err )
81- if ! isTransientUpstreamError (err ) {
84+ if ! isTransientUpstreamError (err ) && ! isTokenPermissionError ( err ) {
8285 sentry .CaptureException (err )
8386 }
8487 writeJSON (w , http .StatusServiceUnavailable , ErrorResponse {Error : msg })
@@ -126,8 +129,11 @@ func (s *Server) handleDetail(w http.ResponseWriter, r *http.Request) {
126129 return
127130 }
128131 msg := "Upstream database is temporarily unavailable — please try again in a moment."
132+ if isTokenPermissionError (err ) {
133+ msg = "DoltHub API token lacks SQL permissions — check token configuration."
134+ }
129135 slog .Error ("detail failed with no stale data" , "error" , err )
130- if ! isTransientUpstreamError (err ) {
136+ if ! isTransientUpstreamError (err ) && ! isTokenPermissionError ( err ) {
131137 sentry .CaptureException (err )
132138 }
133139 writeJSON (w , http .StatusServiceUnavailable , ErrorResponse {Error : msg })
@@ -254,6 +260,13 @@ func isTransientUpstreamError(err error) bool {
254260 return strings .Contains (err .Error (), "no such repository" )
255261}
256262
263+ // isTokenPermissionError returns true if the error indicates the DoltHub API
264+ // token lacks the required permissions (e.g. SQL access). This is a persistent
265+ // configuration issue that won't self-resolve, so it should not flood Sentry.
266+ func isTokenPermissionError (err error ) bool {
267+ return strings .Contains (err .Error (), "API token is not allowed to operate on this resource" )
268+ }
269+
257270// writeUpstreamError classifies DoltHub errors and writes an appropriate response:
258271// - "invalid authorization" → 401 (triggers frontend re-auth)
259272// - other upstream errors → 503 with sanitized message + Sentry capture
@@ -263,11 +276,14 @@ func writeUpstreamError(w http.ResponseWriter, err error, label string) {
263276 return
264277 }
265278 slog .Error (label + " failed" , "error" , err )
266- if ! isTransientUpstreamError (err ) {
279+ if ! isTransientUpstreamError (err ) && ! isTokenPermissionError ( err ) {
267280 sentry .CaptureException (err )
268281 }
269- writeError (w , http .StatusServiceUnavailable ,
270- "Upstream database is temporarily unavailable — please try again in a moment." )
282+ msg := "Upstream database is temporarily unavailable — please try again in a moment."
283+ if isTokenPermissionError (err ) {
284+ msg = "DoltHub API token lacks SQL permissions — check token configuration."
285+ }
286+ writeError (w , http .StatusServiceUnavailable , msg )
271287}
272288
273289// writeMutationError writes a 409 for ConflictError, 400 for everything else.
0 commit comments