What happened
Two problems with how the containment filters build their LIKE patterns. Both return the wrong rows silently; no error, no log line, the query just answers something other than what was asked.
1. Free-text log search is case-sensitive.
internal/adapters/db/log_repo.go:150 uses LIKE where every other text filter in the file uses ILIKE:
query = query.Where("message LIKE ?", "%"+f.TextQuery+"%")
Compare :110 and :116 a few lines above, which are ILIKE, and :623, which has the comment "nobody types a path with the case they logged it in" - the same reasoning applies to messages. Searching error does not find
Error: upload failed, so any message that starts a sentence is invisible to the obvious search for it.
2. % and _ in the search box are treated as wildcards
The pattern is "%"+text+"%" with nothing escaped, at six call sites:
| File |
Line |
Filter |
internal/adapters/db/log_repo.go |
110 |
device_model |
internal/adapters/db/log_repo.go |
116 |
os_name / os_version |
internal/adapters/db/log_repo.go |
150 |
message (free text) |
internal/adapters/db/log_repo.go |
623 |
url (network path) |
internal/adapters/db/project_repo.go |
267 |
projects.name |
So searching 100% matches every message containing 100, and user_id also matches userXid. Both are ordinary characters to whoever typed them.
Worth noting the Flutter side already gets this right — sqflite_source.dart escapes \, % and _ and passes an explicit ESCAPE '\'. This is the same fix on the server.
Suggested fix: one helper in internal/adapters/db, used at all six sites, plus LIKE → ILIKE on line 150:
var likeWildcards = strings.NewReplacer(`\`, `\\`, `%`, `\%`, `_`, `\_`)
func containsPattern(s string) string {
return "%" + likeWildcards.Replace(s) + "%"
}
with each call becoming col ILIKE ? ESCAPE '\'. Backslash is already Postgres's default escape character, so the clause is documentation rather than a behaviour change.
How to reproduce it
- Create a project and upload a batch containing these messages:
Error: upload failed
battery at 100% before sync
battery at 1004 mAh
missing user_id on request
missing userXid on request
- Open the log viewer and search
error
→ expected Error: upload failed, got nothing.
- Search
100%
→ expected only battery at 100% before sync, got that plus battery at 1004 mAh.
- Search
user_id
→ expected only missing user_id on request, got that plus missing userXid on request.
Version
749f973 (main, 2026-08-12)
Server log
What happened
Two problems with how the containment filters build their LIKE patterns. Both return the wrong rows silently; no error, no log line, the query just answers something other than what was asked.
1. Free-text log search is case-sensitive.
internal/adapters/db/log_repo.go:150usesLIKEwhere every other text filter in the file usesILIKE:Compare :110 and :116 a few lines above, which are ILIKE, and :623, which has the comment "nobody types a path with the case they logged it in" - the same reasoning applies to messages. Searching
errordoes not findError: upload failed, so any message that starts a sentence is invisible to the obvious search for it.2.
%and_in the search box are treated as wildcardsThe pattern is
"%"+text+"%"with nothing escaped, at six call sites:internal/adapters/db/log_repo.godevice_modelinternal/adapters/db/log_repo.goos_name/os_versioninternal/adapters/db/log_repo.gomessage(free text)internal/adapters/db/log_repo.gourl(network path)internal/adapters/db/project_repo.goprojects.nameSo searching
100%matches every message containing100, anduser_idalso matchesuserXid. Both are ordinary characters to whoever typed them.Worth noting the Flutter side already gets this right —
sqflite_source.dartescapes\,%and_and passes an explicitESCAPE '\'. This is the same fix on the server.Suggested fix: one helper in
internal/adapters/db, used at all six sites, plusLIKE→ILIKEon line 150:with each call becoming
col ILIKE ? ESCAPE '\'. Backslash is already Postgres's default escape character, so the clause is documentation rather than a behaviour change.How to reproduce it
Error: upload failedbattery at 100% before syncbattery at 1004 mAhmissing user_id on requestmissing userXid on requesterror→ expected
Error: upload failed, got nothing.100%→ expected only
battery at 100% before sync, got that plusbattery at 1004 mAh.user_id→ expected only
missing user_id on request, got that plusmissing userXid on request.Version
749f973 (main, 2026-08-12)
Server log