fix: attach context to startup permission errors (#62) - #63
Merged
thedancingdeveloper merged 4 commits intoAug 10, 2026
Conversation
Directory-creation and TCP-bind failures during startup propagated a raw io::Error with a bare `?`, so anyhow printed nothing but `Permission denied (os error 13)` — no path, no hint. Reproduced locally: pointing data_dir at a non-writable parent produces that exact bare text before the fix. Wrap both with .with_context(), matching the existing pattern in AppConfig::load/save, so failures name the path/address and (for directory creation) hint at the Docker ownership fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
svc-rustnzb/run always passed --port 9090 explicitly, and clap gives an explicit CLI flag precedence over its env fallback, so RUSTNZB_PORT (and config.toml's general.port) were silently unconfigurable in the Docker image despite being documented as a general env var override. Default behavior when neither is set is unchanged (still 9090, matching the Dockerfile's EXPOSE and config.rs's default). Since the image's EXPOSE stays static at build time, document that overriding RUSTNZB_PORT still needs a matching host-side port remap. Add regression tests locking in clap's precedence (env used when no flag, flag wins when both given, None when neither) so a future entrypoint change can't silently reintroduce this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI's self-hosted runner runs cargo test as root, which bypasses the chmod 000 permission check entirely, so create_dir_all under a "locked" parent unexpectedly succeeds and expect_err() panics. Skip the assertions (rather than fail) when the operation unexpectedly succeeds under elevated privileges, matching how these tests already behave correctly for a non-root user. Verified locally both as a regular user (real EACCES exercised) and via sudo (early-return path exercised, no panic). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
container-smoke, release-image, and the container-canary workflow all build the runtime Dockerfile from scratch, pulling alpine:3.23 unauthenticated. The shared self-hosted runner's IP hits Docker Hub's anonymous pull rate limit (429) under normal CI load, unrelated to any code change. Log in to docker.io with the DOCKERHUB_TOKEN/DOCKERHUB_USERNAME secrets (falls back to anonymous pulls if unset) before building, mirroring the existing repo.indexarr.net/ghcr.io login pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
thedancingdeveloper
deleted the
fix/62-startup-permission-error-context
branch
August 10, 2026 10:23
Merged
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two fixes for #62:
1. Startup errors now carry context. Two
?-propagatedio::Errors had no.context(), so a permission failure crashed the process with nothing but a barePermission denied (os error 13)— no path, no address, no hint. This matches the reporter's log verbatim.crates/nzb-web/src/startup.rs—create_dir_allfordata_dir/incomplete_dir/complete_dirnow goes through acreate_data_dir()helper that wraps the error with the failing path and a Docker-ownership hint (mirrors the existing pattern inAppConfig::load/save).apps/rustnzb/src/server.rs—TcpListener::bindinserve()now wraps the error with the bind address.Reproduced locally before the fix: pointing
data_dirat a path under achmod 000parent produced the anyhow{:?}outputPermission denied (os error 13)— nothing else — confirming this is a real, reachable source of the reported crash text. (The other candidate, a privileged-port bind failure, is a secondary/unlikely cause here since the Docker image hardcoded--port 9090— see below.)2.
RUSTNZB_PORTwas dead in the Docker image.svc-rustnzb/runalways passed--port 9090explicitly, and clap gives an explicit CLI flag precedence over its env fallback, soRUSTNZB_PORT(andconfig.toml'sgeneral.port) were silently unconfigurable in Docker despite being documented as a general env var override. Removed the hardcoded flag so the env var (and config file) actually take effect; default when neither is set is unchanged (still9090, matching the Dockerfile'sEXPOSEandconfig.rs's default). Since the image'sEXPOSEstays static at build time, documented that overridingRUSTNZB_PORTstill needs a matching host-side port remap (-p host:$RUSTNZB_PORT).This is a diagnosability + configuration fix, not a fix for the reporter's underlying Synology permission/ACL problem — it turns an untraceable bare crash into an actionable error, and makes the documented
RUSTNZB_PORTbehavior actually true. Still waiting on the reporter for their config values and full log to confirm which exact path fired; see issue comment: #62 (comment)Test plan
cargo test -p nzb-web --lib— unit tests forcreate_data_dir: positive (nested dir creation succeeds) and negative (non-writable parent produces context-bearing error,#[cfg(unix)])cargo test -p nzb-web --test startup_directory_errors— integration tests: positive (custom, non-default incomplete/complete/data dirs succeed), negative (regression guard asserting the error is not the barePermission denied (os error 13)text from "Permission denied" in Synology container manager #62, and does contain the failing path)cargo test -p rustnzb --test server_bind_error_test— integration tests: positive (bind to a free port succeeds), negative (bind to an already-occupied address fails with context naming the address)cargo test -p rustnzb --bin rustnzb— regression tests locking in clap's--port/RUSTNZB_PORTprecedence (env used when no flag; flag wins when both given;Nonewhen neither), so a future entrypoint change can't silently reintroduce the hardcoding bugcargo test -p nzb-web --tests(69 unit + all integration),cargo test -p rustnzb --tests(all e2e/integration)cargo fmt --check/cargo clippy --all-targets -- -D warningsclean on both cratesReferences #62.
🤖 Generated with Claude Code