Skip to content

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=50

Don’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...xyz12345678

If the | piece is stripped during curl or a shell variable, quote it:

Terminal window
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:

compose.yml
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/target

oxide 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.

  1. The stored hash uses BCRYPT_ROUNDS that 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.
  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.