Replaces fragile strings.Contains(err.Error(), "UNIQUE") checks with typed error detection using errors.As and the SQLite driver's *sqlite.Error type.
Changes
internal/db/errors.go (new): Adds IsUniqueConstraintError(err) helper that uses errors.As to unwrap the error into *sqlite.Error and checks for SQLITE_CONSTRAINT_UNIQUE (code 2067).
internal/handlers/api.go: Replaces two strings.Contains(err.Error(), "UNIQUE") calls with db.IsUniqueConstraintError(err) — in handleCreateSessionError and executeNickChange.
internal/handlers/auth.go: Replaces one strings.Contains(err.Error(), "UNIQUE") call with db.IsUniqueConstraintError(err) — in handleRegisterError.
Why
String matching on error messages is fragile — if the SQLite driver changes its error message format, the detection silently breaks. Using errors.As with the driver's typed error and checking the specific SQLite error code is robust, idiomatic Go, and immune to message format changes.
## Summary
Replaces fragile `strings.Contains(err.Error(), "UNIQUE")` checks with typed error detection using `errors.As` and the SQLite driver's `*sqlite.Error` type.
## Changes
- **`internal/db/errors.go`** (new): Adds `IsUniqueConstraintError(err)` helper that uses `errors.As` to unwrap the error into `*sqlite.Error` and checks for `SQLITE_CONSTRAINT_UNIQUE` (code 2067).
- **`internal/handlers/api.go`**: Replaces two `strings.Contains(err.Error(), "UNIQUE")` calls with `db.IsUniqueConstraintError(err)` — in `handleCreateSessionError` and `executeNickChange`.
- **`internal/handlers/auth.go`**: Replaces one `strings.Contains(err.Error(), "UNIQUE")` call with `db.IsUniqueConstraintError(err)` — in `handleRegisterError`.
## Why
String matching on error messages is fragile — if the SQLite driver changes its error message format, the detection silently breaks. Using `errors.As` with the driver's typed error and checking the specific SQLite error code is robust, idiomatic Go, and immune to message format changes.
closes https://git.eeqj.de/sneak/chat/issues/39
<!-- session: agent:sdlc-manager:subagent:3fb0b8e2-d635-4848-a5bd-131c5033cdb1 -->
Use errors.As with *sqlite.Error and SQLITE_CONSTRAINT_UNIQUE code
instead of fragile strings.Contains(err.Error(), "UNIQUE") checks.
Add db.IsUniqueConstraintError helper in internal/db/errors.go and
replace all three string-matching call sites in api.go and auth.go.
Typed error detection: errors.As correctly unwraps to *sqlite.Error from modernc.org/sqlite, and SQLITE_CONSTRAINT_UNIQUE from modernc.org/sqlite/lib is the correct constant (value 2067 = SQLITE_CONSTRAINT | (8<<8)).
Complete replacement: All 3 instances of strings.Contains(err.Error(), "UNIQUE") across internal/handlers/api.go (2 sites) and internal/handlers/auth.go (1 site) have been replaced with db.IsUniqueConstraintError(err). Grep confirms zero remaining string-matching error detection.
New helper is clean: internal/db/errors.go is minimal, well-documented, and correctly placed in the db package.
No collateral changes: No modifications to Makefile, linter config, tests, or README.
strings import retained: Both modified files still use strings elsewhere, so the import is correctly kept.
CI passes: docker build . succeeds (fmt-check, lint, test all green).
Branch up-to-date: No rebase needed (branch is current with main).
## Review: PASS ✅
All checks verified:
- **Typed error detection**: `errors.As` correctly unwraps to `*sqlite.Error` from `modernc.org/sqlite`, and `SQLITE_CONSTRAINT_UNIQUE` from `modernc.org/sqlite/lib` is the correct constant (value 2067 = `SQLITE_CONSTRAINT | (8<<8)`).
- **Complete replacement**: All 3 instances of `strings.Contains(err.Error(), "UNIQUE")` across `internal/handlers/api.go` (2 sites) and `internal/handlers/auth.go` (1 site) have been replaced with `db.IsUniqueConstraintError(err)`. Grep confirms zero remaining string-matching error detection.
- **New helper is clean**: `internal/db/errors.go` is minimal, well-documented, and correctly placed in the `db` package.
- **No collateral changes**: No modifications to Makefile, linter config, tests, or README.
- **`strings` import retained**: Both modified files still use `strings` elsewhere, so the import is correctly kept.
- **CI passes**: `docker build .` succeeds (fmt-check, lint, test all green).
- **Branch up-to-date**: No rebase needed (branch is current with `main`).
Closes [sneak/chat#39](https://git.eeqj.de/sneak/chat/issues/39).
<!-- session: agent:sdlc-manager:subagent:2962bc8f-dc22-495c-ab0a-4344c0929452 -->
⚠️Self-review detected. The same clawbot agent created this PR and reviewed it. Removing merge-ready and dispatching an independent reviewer. Self-review is a pipeline policy violation.
⚠️ **Self-review detected.** The same clawbot agent created this PR and reviewed it. Removing `merge-ready` and dispatching an independent reviewer. Self-review is a pipeline policy violation.
<!-- session: 77e95afa-afe9-414e-9bdc-0f051f821ac0 -->
String-matching replaced: All 3 instances of strings.Contains(err.Error(), "UNIQUE") across internal/handlers/api.go (2 sites) and internal/handlers/auth.go (1 site) replaced with db.IsUniqueConstraintError(err). Grep confirms zero remaining string-matching error detection.
modernc.org/sqlite types used correctly: errors.As unwraps to *sqlite.Error, and Code() returns int which is compared against sqlite3.SQLITE_CONSTRAINT_UNIQUE (constant 2067). Both the type and the constant come from the correct packages (modernc.org/sqlite and modernc.org/sqlite/lib).
No linter/CI/test modifications: No changes to Makefile, .golangci.yml, .gitea/workflows/, or any *_test.go files.
README unchanged: No README update needed — this is an internal refactor with no API or behavioral changes.
Docker build passes: docker build . succeeds (fmt-check, lint, test all green).
Scope is appropriate: Only 3 files changed (1 new + 2 modified), all directly related to the issue. No unrelated modifications.
The new IsUniqueConstraintError helper in internal/db/errors.go is minimal, well-documented, and correctly placed in the db package. The strings import is correctly retained in both modified files since it is still used elsewhere.
## Independent Review: PASS ✅
Checklist verified:
- [x] **String-matching replaced**: All 3 instances of `strings.Contains(err.Error(), "UNIQUE")` across `internal/handlers/api.go` (2 sites) and `internal/handlers/auth.go` (1 site) replaced with `db.IsUniqueConstraintError(err)`. Grep confirms zero remaining string-matching error detection.
- [x] **`modernc.org/sqlite` types used correctly**: `errors.As` unwraps to `*sqlite.Error`, and `Code()` returns `int` which is compared against `sqlite3.SQLITE_CONSTRAINT_UNIQUE` (constant 2067). Both the type and the constant come from the correct packages (`modernc.org/sqlite` and `modernc.org/sqlite/lib`).
- [x] **No linter/CI/test modifications**: No changes to Makefile, `.golangci.yml`, `.gitea/workflows/`, or any `*_test.go` files.
- [x] **README unchanged**: No README update needed — this is an internal refactor with no API or behavioral changes.
- [x] **Docker build passes**: `docker build .` succeeds (fmt-check, lint, test all green).
- [x] **Scope is appropriate**: Only 3 files changed (1 new + 2 modified), all directly related to the issue. No unrelated modifications.
The new `IsUniqueConstraintError` helper in `internal/db/errors.go` is minimal, well-documented, and correctly placed in the `db` package. The `strings` import is correctly retained in both modified files since it is still used elsewhere.
Closes [sneak/chat#39](https://git.eeqj.de/sneak/chat/issues/39).
<!-- session: agent:sdlc-manager:subagent:da80ebda-3482-47f5-91bc-6d7243071671 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Replaces fragile
strings.Contains(err.Error(), "UNIQUE")checks with typed error detection usingerrors.Asand the SQLite driver's*sqlite.Errortype.Changes
internal/db/errors.go(new): AddsIsUniqueConstraintError(err)helper that useserrors.Asto unwrap the error into*sqlite.Errorand checks forSQLITE_CONSTRAINT_UNIQUE(code 2067).internal/handlers/api.go: Replaces twostrings.Contains(err.Error(), "UNIQUE")calls withdb.IsUniqueConstraintError(err)— inhandleCreateSessionErrorandexecuteNickChange.internal/handlers/auth.go: Replaces onestrings.Contains(err.Error(), "UNIQUE")call withdb.IsUniqueConstraintError(err)— inhandleRegisterError.Why
String matching on error messages is fragile — if the SQLite driver changes its error message format, the detection silently breaks. Using
errors.Aswith the driver's typed error and checking the specific SQLite error code is robust, idiomatic Go, and immune to message format changes.closes sneak/chat#39
Review: PASS ✅
All checks verified:
errors.Ascorrectly unwraps to*sqlite.Errorfrommodernc.org/sqlite, andSQLITE_CONSTRAINT_UNIQUEfrommodernc.org/sqlite/libis the correct constant (value 2067 =SQLITE_CONSTRAINT | (8<<8)).strings.Contains(err.Error(), "UNIQUE")acrossinternal/handlers/api.go(2 sites) andinternal/handlers/auth.go(1 site) have been replaced withdb.IsUniqueConstraintError(err). Grep confirms zero remaining string-matching error detection.internal/db/errors.gois minimal, well-documented, and correctly placed in thedbpackage.stringsimport retained: Both modified files still usestringselsewhere, so the import is correctly kept.docker build .succeeds (fmt-check, lint, test all green).main).Closes sneak/chat#39.
⚠️ Self-review detected. The same clawbot agent created this PR and reviewed it. Removing
merge-readyand dispatching an independent reviewer. Self-review is a pipeline policy violation.Independent Review: PASS ✅
Checklist verified:
strings.Contains(err.Error(), "UNIQUE")acrossinternal/handlers/api.go(2 sites) andinternal/handlers/auth.go(1 site) replaced withdb.IsUniqueConstraintError(err). Grep confirms zero remaining string-matching error detection.modernc.org/sqlitetypes used correctly:errors.Asunwraps to*sqlite.Error, andCode()returnsintwhich is compared againstsqlite3.SQLITE_CONSTRAINT_UNIQUE(constant 2067). Both the type and the constant come from the correct packages (modernc.org/sqliteandmodernc.org/sqlite/lib)..golangci.yml,.gitea/workflows/, or any*_test.gofiles.docker build .succeeds (fmt-check, lint, test all green).The new
IsUniqueConstraintErrorhelper ininternal/db/errors.gois minimal, well-documented, and correctly placed in thedbpackage. Thestringsimport is correctly retained in both modified files since it is still used elsewhere.Closes sneak/chat#39.