fix: start the backend without a preconfigured environment - #305
Merged
Conversation
This was referenced Aug 25, 2026
`go run ./cmd/traceway` panicked twice on a fresh clone: DB_TYPE had no default even though the no-build-tag build is dual-SQLite, and PORTS fell back to "80,8082" whose first entry cannot bind as a non-root user. Every entry point hit this -- a fresh clone, an IDE run, CI, Docker -- so the workaround kept being re-invented per environment (most recently as env exports in the Nix dev shell, which only helped Nix users and silently shadowed backend/.env, since godotenv.Load never overwrites a set var). DB_TYPE now defaults in config.defaultDBType, guarded by build tag: "sqlite" without transactional_pg, "" with it so that build stays byte-for-byte unchanged. One definition rather than one per call site -- db_transactional_sqlite.go, db_telemetry_sqlite.go and db_telemetry_duckdb.go all test `DBType != "sqlite"`, and patching each was how the first attempt at this left a nil telemetry DB. Ports are now bound up front rather than inside the serving goroutines, so a bind failure is known before the process reports itself started. Individual failures are tolerated with a warning and a CaptureException; only losing every port is fatal. Previously just the first entry was bound on the main goroutine and panicked, while the rest panicked inside goroutines guarded by traceway.Recover(), which swallows the panic and reports nothing at all when monitoring is unconfigured -- so the same failure was either fatal or invisible depending on the order of PORTS. serveHTTP (added by #300) now takes an already-bound net.Listener and calls srv.Serve instead of srv.ListenAndServe. Its ReadHeaderTimeout / ReadTimeout / IdleTimeout are the point of that helper and are kept verbatim: gin's RunListener sets none of them, so binding the listener separately had to go through the same http.Server rather than around it. JWT_SECRET keeps having no default, deliberately: a key committed to the repo would be identical in every clone, and persisting a generated one has no clean home in the transactional_pg telemetry_ch build. It now exits 1 with the existing message plus a generation hint instead of burying it under a panic stack trace. Verified after rebasing onto the merged #296/#297/#300: with only JWT_SECRET set and running as non-root, the server warns about :80, serves :8082, and reaches /version with 0 panics; a partial request is still cut off at exactly 15s, confirming #300's slowloris timeouts survived the change; all three build-tag combinations compile; the full backend test suite passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FrameAutomata
force-pushed
the
fix/backend-startup-defaults
branch
from
August 25, 2026 20:12
2c16c55 to
1c61827
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
go run ./cmd/tracewaypanics twice on a fresh clone:DB_TYPEhas no default, so it falls through toinitPostgres()— in a build whose migration runner applies SQLite-dialect migrations unconditionallyPORTSfalls back to80,8082, and:80cannot bind as a non-root userEvery entry point hits this — fresh clone, IDE run, CI, Docker — so the workaround kept getting re-invented per environment. Most recently as env exports in the Nix dev shell (#302), which only helped Nix users and silently shadowed
backend/.env, becausegodotenv.LoadcallsloadFile(name, false)and never overwrites an already-set var.Fixing it here means #302 can delete those exports entirely rather than patch them.
DB_TYPE
Defaults in
config.defaultDBType, guarded by build tag:"sqlite"withouttransactional_pg,""with it so that build stays byte-for-byte unchanged.One definition, not one per call site.
db_transactional_sqlite.go,db_telemetry_sqlite.goanddb_telemetry_duckdb.goall testDBType != "sqlite"— my first attempt patched only the first and left a nil telemetry DB (database/sql.(*DB).conn(0x0, ...)), which is exactly the duplication this avoids.runMainDBMigrationsignores the value in the no-tags build, so migrations were never at risk.PORTS
Ports are now bound up front rather than inside the serving goroutines, so a bind failure is known before the process reports itself started. Individual failures warn +
CaptureException; only losing every port is fatal.The old behavior was worse than "panics on :80" — it was inconsistent:
PORTS=80,8082:80fails → fatal panicPORTS=8082,80:80fails → silently swallowed, runs on:8082with no outputOnly
portsList[0]bound on the main goroutine; the rest panicked inside goroutines guarded bytraceway.Recover(), which callsrecover()and — whencollectionFrameStore == nil, i.e. monitoring unconfigured — returns without reporting anything. The default80,8082is unchanged, since it's a documented interface and all four DockerfilesEXPOSE 80 8082.JWT_SECRET
Still no default, deliberately: a key committed to the repo is identical in every clone, and persisting a generated one has no clean home in the
transactional_pg telemetry_chbuild. It now exits 1 with the existing message plusopenssl rand -hex 32instead of burying it under a panic stack trace.Verification
JWT_SECRETset, non-root: warns about:80, serves:8082,/version→ HTTP 200, 0 panicsPORTS=80alone → exit 2,could not listen on any portJWT_SECRET→ exit 1, clean message, 0 stack framestransactional_pg telemetry_ch,telemetry_duckdb)Follow-up
Once this and #301 both land, the env-var paragraph #301 adds to CLAUDE.md should shrink to just
JWT_SECRET—DB_TYPEandPORTSstop being things you have to set.🤖 Generated with Claude Code