Skip to content

Troubleshooting

devituz edited this page Jun 3, 2026 · 1 revision

Troubleshooting

Concrete errors, the cause, and the fix. If the problem you're seeing isn't here, the Discussions tab is the right place to ask.


database: driver "postgres" not registered

You forgot the blank import that registers the driver. Every binary that opens a connection needs to import its driver package for its side-effects:

import (
    _ "github.com/devituz/lagodev/drivers/postgres"  // ← this
    // or "/mysql" / "/sqlite"
)

go test shows the same error: each test binary needs the driver package imported somewhere it compiles into.

sql: unknown driver "sqlite3" (or "postgres" / "mysql")

This is the stdlib driver registration, not lagodev's. The underlying driver package (mattn/go-sqlite3, lib/pq, go-sql-driver/mysql) hasn't been imported. Adding _ "github.com/devituz/lagodev/drivers/sqlite" (or /postgres, /mysql) fixes both — the lagodev wrappers blank-import the underlying driver in turn.

migrations: lock acquire timeout

Another process is holding the migration lock. Causes:

  • A previous run crashed mid-migration on a dialect without per-connection lock cleanup (rare; Postgres releases on connection close, but a hung connection can hold a lock until TCP timeout).
  • Two CI jobs are deploying the same database at the same time.

Fix:

-- Postgres
SELECT pg_advisory_unlock_all();          -- in a fresh session
SELECT pid, * FROM pg_stat_activity WHERE state = 'idle in transaction';
-- terminate the holder, then re-run migrate

-- MySQL / SQLite
DELETE FROM migrations_lock;              -- last-resort manual cleanup

Bound the wait with lago migrate --lock-timeout=30s so a stuck deploy fails fast in CI instead of hanging the pipeline.

migrations: checksum mismatch for "X"

You edited a migration that's already been applied. The runner hashes the emitted SQL of each applied migration and refuses to proceed if a re-emission produces a different hash, which prevents "works on my machine because I changed Up() after deploying it" divergence.

The right fix is almost always a new migration that does the extra work — never re-edit history.

If you genuinely need to bypass (typically: a non-functional formatting tweak to the SQL), pass --no-enforce-checksums to the migrate command, but understand you're acknowledging the production schema may now differ from what Up() would produce.

pq: SSL is not enabled on the server

Postgres SSL handshake failed. Fix in .env:

DB_SSL_MODE=disable          # local dev
# or
DB_SSL_MODE=require          # production with TLS

Available modes: disable, allow, prefer, require, verify-ca, verify-full. The default is empty (driver default).

Error 1064: You have an error in your SQL syntax on MySQL

Usually one of:

  • An identifier conflicting with a reserved word (order, group). The schema DSL quotes everything automatically — if you wrote raw SQL via WhereRaw or conn.QueryContext, add backticks yourself.
  • An unsupported feature for your MySQL version. The schema DSL targets 5.7+; on 5.6 some Default() expressions get rejected.

dial tcp 127.0.0.1:5432: connect: connection refused

The DB process isn't reachable from where the program is running.

  • Docker: forgot -p 5432:5432, or the container is on a custom network the host isn't on.
  • Compose: app and DB are in the same docker-compose.yml but DB_HOST=127.0.0.1 instead of the service name (DB_HOST=db).
  • macOS: 127.0.0.1 from a container points at the container, not the host. Use host.docker.internal.

web: listen tcp :8080: bind: address already in use

Another process is on the port. Find and stop it:

lsof -i :8080 -sTCP:LISTEN
# kill the PID, or switch ports:
APP_ADDR=:8081 go run .

A common culprit is a previous go run . that wasn't cleanly shut down — pkill -f "go run ./examples/secure" or similar.

c.Bind returns "json: unknown field"

The struct didn't declare the incoming field. c.Bind calls DisallowUnknownFields() so typos in client payloads surface loudly. Either:

  • Add the field to your struct (the right answer if it's legit),
  • Tag it with json:"-" to silence the decoder (rare),
  • Reject the request as 400 (current behaviour — you saw this message because Bind already wrote the 400).

This is documented in Web-Framework.

c.Bind returns "http: request body too large"

The body exceeded the active body limit. Defaults:

  • c.Bind applies a 1 MiB cap via http.MaxBytesReader when no web.BodyLimit(n) middleware is active.
  • With web.BodyLimit(n) applied, that lower limit wins.

Raise the limit:

app.Use(web.BodyLimit(10 << 20))   // 10 MiB

For genuinely huge uploads, stream the body to disk / S3 directly rather than decoding into a struct.

CSRF middleware returns 403 on POST

web.CSRF() requires a matching token in both the cookie and the X-CSRF-Token header (or the _csrf form field). Either:

  • Set web.CSRF() only on routes that need it (don't apply globally to your API if it's auth'd by JWT — JWT-only APIs are CSRF-immune).
  • Make a GET request first so the middleware issues the cookie, then echo it back in the header for subsequent unsafe methods.

See the Web-Framework secure-by-default section.

Logger prints in Uzbek

Yes — the framework's default Logger() middleware prints status messages in Uzbek (server tinglamoqda, ro'yxatdan o'tgan marshrutlar). Replace the logger to localise:

app := web.New(/* ... */)
// override the auto-installed middleware with your own logger:
// (advanced) construct a custom Router and pass it in.

A simpler workaround is to grep the strings out of structured logs; status codes and request paths are still the canonical events.

lago: command not found

go install puts the binary in $(go env GOPATH)/bin. That path isn't on your PATH by default:

export PATH="$(go env GOPATH)/bin:$PATH"
# add to ~/.zshrc or ~/.bashrc

lago.json: malformed JSON

The CLI falls back to defaults and prints the parse error on stderr. Fix the JSON and re-run any make:* — the path configuration takes effect immediately.

A factory throws "no field with column X"

The factory definition refers to a struct field that doesn't exist on the model, or the model has a column:"…" tag that doesn't match. Cross-check the column name resolved by the reflection cache:

import "github.com/devituz/lagodev/internal/reflectutil"

sch, _ := reflectutil.For(reflect.TypeOf(models.User{}))
fmt.Printf("%+v\n", sch.Columns)   // map[string]*Field — keys are column names

(Internal package — exported via the test helpers; don't depend on it from production code.)

Migrations on SQLite leave a *.db-shm / *.db-wal file

Normal — those are SQLite's WAL companion files. .gitignore already excludes them (*.db, *.sqlite, *.sqlite3). They're deleted automatically when the database is closed cleanly.

go test hangs on startup

Almost always a connection-pool problem in tests that opened a real database. Switch the test to lagotest.SQLite(t) (in-memory, fresh per test) — see the Getting-Started testing section.

See also

  • FAQ — design questions, not error messages.
  • ConfigurationDB_* reference.
  • Architecture — when the error makes more sense after seeing the layers.

Clone this wiki locally