Skip to content

fix: return 4xx for client errors on canvas repository file endpoint#5245

Draft
cursor[bot] wants to merge 1 commit into
mainfrom
cursor/agent-aef80127
Draft

fix: return 4xx for client errors on canvas repository file endpoint#5245
cursor[bot] wants to merge 1 commit into
mainfrom
cursor/agent-aef80127

Conversation

@cursor

@cursor cursor Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Problem

Sentry surfaced repeated HTTP 500 /api/v1/canvases/{canvas_id}/repository/file errors from production
(issue 7539528545).

The public download handler (pkg/public/repository_file_download.go) wraps canvases.ReadRepositorySpecFile and treats every error from it as an internal server error, even though that helper returns proper gRPC status codes:

  • codes.NotFound — canvas live version missing, version not found, change request not found
  • codes.PermissionDenied — draft owned by another user, version not visible in current flow
  • codes.Unauthenticated — no user in context
  • codes.InvalidArgument — bad organization_id, bad canvas_id, bad version_id, unsupported spec path

All of these are client-caused but were being mapped to HTTP 500. The logging middleware then forwarded each one to Sentry via hub.CaptureMessage(...), which uses sentry.LevelInfo — exactly matching the level/title shape we saw in the Sentry issue (HTTP 500 ..., level info).

Fix

Map the gRPC status code carried by ReadRepositorySpecFile back to the matching HTTP status:

gRPC code HTTP
NotFound 404
PermissionDenied 403
Unauthenticated 401
InvalidArgument / FailedPrecondition / OutOfRange 400
anything else 500 (still logged at error level)

Client errors now pass the gRPC Message() through to the response body so the UI/CLI can surface a useful hint, and they are logged at debug level instead of error so they don't pollute the server logs.

The git-backed file path (gitProvider.GetFile) is unchanged: those errors already log "Failed to get file" and return 500, and the existing test asserts that mapping for missing files.

Tests

  • New Test__RepositoryFileDownload cases covering canvas.yaml / console.yaml success and the 400 / 404 mapping for invalid / missing version_id (require the dev DB).
  • New pure-Go unit test TestRepositorySpecFileHTTPStatus that exercises the helper directly without a database, covering all mapped codes plus a non-gRPC error fallback.

Verification

  • go build ./pkg/public/... ./pkg/grpc/actions/canvases/...
  • go vet ./pkg/public/...
  • go test -run TestRepositorySpecFileHTTPStatus ./pkg/public/
  • gofmt -l clean ✅
Open in Web Open in Cursor 

Previously, the public /api/v1/canvases/{canvas_id}/repository/file
endpoint mapped every error from ReadRepositorySpecFile to HTTP 500.
That meant client-caused errors -- canvas without a live version
(NotFound), draft owned by another user (PermissionDenied), invalid
version_id query string (InvalidArgument) -- all surfaced as 500
responses and got captured by the public middleware as Sentry HTTP
errors.

Map the gRPC status code carried by ReadRepositorySpecFile to a
matching 4xx response so only true server errors (Internal/Unknown)
are returned as 500, and only those reach Sentry. Pass the gRPC
message through for client errors so the UI/CLI can surface a useful
hint, and keep error-level logging only for actual 5xx responses.
@superplanehq-integration

Copy link
Copy Markdown

👋 Commands for maintainers:

  • /sp start - Start an ephemeral machine (takes ~30s)
  • /sp stop - Stop a running machine (auto-executed on pr close)

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.

1 participant