gofr-dev/gofr · error
file does not have read permission: %w
Error message
file does not have read permission: %w
What it means
errReadPermissionDenied wraps fs.ErrPermission and is produced by validateFile in pkg/gofr/http/router.go when a static file matched by the router has a mode with no read bit set. It exists so files lacking the read permission bit are reported identically to a real EACCES from os.Open, ensuring both routes reach respondWithFileError with the same answer.
Source
Thrown at pkg/gofr/http/router.go:38
DefaultSwaggerFileName = "openapi.json"
staticServerNotFoundFileName = "404.html"
staticServerIndexFileName = "index.html"
// RouterEnvVar selects the route matcher. Unset (or any unrecognized value)
// means MatcherMux, so the default behavior is unchanged.
RouterEnvVar = "GOFR_ROUTER"
// MatcherMux is gorilla/mux's linear scan — the default.
MatcherMux = "mux"
// MatcherTrie is the opt-in segment-trie index, O(path length) in the number
// of registered routes.
MatcherTrie = "trie"
)
// errReadPermissionDenied wraps fs.ErrPermission so that a file whose mode carries no read bit is
// reported the same way a real EACCES from os.Open is — the two reach respondWithFileError by
// different routes and must not answer differently.
var errReadPermissionDenied = fmt.Errorf("file does not have read permission: %w", fs.ErrPermission)
// Router is responsible for routing HTTP request.
type Router struct {
mux.Router
RegisteredRoutes *[]string
// useTrie selects the O(path) trie matcher (GOFR_ROUTER=trie) over mux's
// default O(n) linear scan. When false, ServeHTTP delegates to mux exactly
// as before, so the default behavior is byte-for-byte unchanged.
useTrie bool
// idx is the trie index. It is built once, lazily, on the first request,
// from the routes registered up to that point. This is correct for GoFr's
// lifecycle: every route is registered during startup (app.GET/POST/...,
// the GraphQL route, the static/catch-all handlers) before the server
// accepts its first request, and GoFr does not add routes afterwards. A
// route registered after the first request would not be reflected in the
// trie index — a deliberate trade for a lock-free steady state, matching
// GoFr's static-routing model. buildIdx guards that one-time build.View on GitHub (pinned to 187eb24962)
Solutions
- chmod the file so the serving process's user has read permission (e.g. chmod 644 file)
- Check ownership with ls -l and chown to the user running the gofr service
- Verify the static-file path configured on the router points to readable assets
- Run the container/process with a user that has read access to the asset directory
Example fix
// before (shell) -rw------- 1 root root index.html // after (shell) chmod 644 index.html # or chown appuser:appuser index.html
Defensive patterns
Strategy: validation
Validate before calling
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("static file unavailable: %w", err)
}
if info.Mode().Perm()&0o400 == 0 {
return fmt.Errorf("file lacks read permission: %s", path)
} Type guard
func isReadPermissionDenied(err error) bool {
return errors.Is(err, fs.ErrPermission)
} Try / catch
if err := serveFile(w, r, path); err != nil {
if errors.Is(err, fs.ErrPermission) {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
http.Error(w, "not found", http.StatusNotFound)
} Prevention
- chmod 644 static assets in CI before packaging images
- Run the service as a user with read access to asset directories
- Add a startup check that walks static dirs and verifies readability
- Avoid running containers as root then switching to unprivileged users without chown
When it happens
Trigger: Serving static files via the router where the requested file's permission mode lacks the read bit (e.g. mode 0200 or 0600 owned by another user), so validateFile rejects it before/instead of the OS open failing.
Common situations: Deploying static assets with restrictive umask; files copied by CI as root then served by an unprivileged process; Docker image layers with wrong file ownership/mode.
Related errors
- failed to create migration directory %q: %w
- unexpected error stating migration file: %w
- failed to open existing migration file: %w
- failed to delete object
- failed to read CA certificate from %s: %w
AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01).
Data as JSON: /api/errors/b71f3a35fc3a069e.
Report an issue: GitHub.