Troubleshooting
“Database not installed — register DatabaseServiceProvider before use”
Cause: a handler called conn() or Model::find_id(...) before
DatabaseServiceProvider::register ran.
Fix: confirm DatabaseServiceProvider is in bootstrap.rs:
app.register(ConfigServiceProvider) .register(DatabaseServiceProvider) // must be registered .register(AppServiceProvider) .register(RouteServiceProvider) .boot();Order matters — DatabaseServiceProvider must run before any provider
that queries the DB at registration time.
”Migration file of version ‘m2026_…’ is missing, this migration has been applied but its file is missing”
Cause: a migration row exists in the migrations table (or
seaql_migrations on older projects), but the matching .rs file was
deleted or renamed.
Fix (development): oxide migrate:fresh drops every table and
replays the migrations.
Fix (production, or when data must be preserved): manually align the tracking table with the code. If the file was renamed:
UPDATE migrations SET version = 'm2026_04_21_110322_create_whatever_table' WHERE version = 'm2026_04_21_000003_create_whatever_table';Or delete the row if the migration should be rerun:
DELETE FROM migrations WHERE version = 'm2026_…_create_whatever_table';“insert failed: mismatched types; Rust type Option<i64> (SQL type BIGINT) vs BIGINT UNSIGNED”
Cause: entity declared pub id: i64 (signed) but the migration or
pre-existing table uses BIGINT UNSIGNED.
Fix: the Oxide Blueprint t.id() emits signed BIGINT for
portability. Run migrate:fresh in dev to realign. For prod, migrate
the column type:
ALTER TABLE your_table MODIFY id BIGINT NOT NULL AUTO_INCREMENT;413 Payload Too Large
Cause: request body exceeded APP_BODY_MAX_MB (default 10 MB).
Fix: raise the cap for that service if it legitimately needs
larger uploads. Put the knob in .env:
APP_BODY_MAX_MB=50Don’t raise this past what your upstream (ingress / nginx / load balancer) will pass.
408 Request Timeout
Cause: a handler took longer than APP_REQUEST_TIMEOUT_SECS
(default 30).
Fix: profile the handler. If the work is legitimately long-running
(report generation, imports), move it into a background task. If the
handler shouldn’t be that slow, your DB is probably the culprit — add
tracing::info_span! around suspicious queries and read the logs.
401 on every protected route right after login
Cause: the issued token was copied without the {id}| prefix. The
whole returned string is the token.
Fix: use the full value. A correct Authorization header looks
like:
Authorization: Bearer 42|abcd...xyz12345678If the | piece is stripped during curl or a shell variable, quote it:
TOKEN='42|abcd...xyz12345678'curl -H "Authorization: Bearer $TOKEN" ...403 “Missing ability: X”
Cause: the token’s abilities array doesn’t contain X and isn’t
["*"].
Fix: issue a new token with the required ability, or use the
wildcard token. Confirm the token’s abilities via the /me-style
endpoint (it returns current_token.abilities).
oxide: command not found inside Docker
Cause: the CLI was installed into a volume that isn’t shared with the container.
Fix: install the CLI into the container’s CARGO_HOME:
services: app: command: - sh - -c - | if ! command -v oxide >/dev/null 2>&1; then cargo install --path crates/oxide-cli --quiet fi exec cargo watch -w crates -x 'run -p your-app' volumes: - ./:/app:delegated - cargo-home:/usr/local/cargo # persists `oxide` binary - target:/app/targetoxide vendor:publish X says “no publishables found for tag X”
Cause: no resolved dependency ships database/migrations/X/ or
config/X.toml.
Fix: confirm you’re inside a project whose Cargo.toml depends on
the crate that ships those stubs. cargo tree -p oxide-auth (or
whichever) should show it. If running from the framework workspace
itself, default-members must be set so the CLI knows which project
is the consumer.
bcrypt verify returns false for a valid password
Cause: two candidates.
- The stored hash uses
BCRYPT_ROUNDSthat the current build doesn’t match. This is fine — bcrypt hashes include the cost, so any build can verify any cost. If verify still fails, check #2. - The hash was stored with leading/trailing whitespace (common when
copied through a form). Trim the input before calling
Hash::check.
”migration already exists” from oxide make:migration
Cause: a file with the same name (minus timestamp) already lives
in src/database/migrations/.
Fix: wait a second and retry — the timestamp resolves to second-precision. Or pick a different name. Or delete the old one if it was scaffolding you abandoned.
Build fails after framework tag bump
Cause: minor version bump in pre-1.0 Oxide can break API. Check
CHANGELOG.md on the framework repo.
Fix: read the changelog entry for the target version and apply
the listed migrations. If blocked, pin back to the previous tag in
your application’s Cargo.toml and raise the issue with your team.
Tracing output is silent
Cause: RUST_LOG filter is omitting your messages, or
tracing_subscriber::fmt() never initialized.
Fix: confirm tracing_subscriber is initialized in main.rs
(the template does this). Set RUST_LOG=debug temporarily to see
everything, then narrow to info,sqlx=warn for normal operation.