PR #34 added exqlite (SQLite NIF) as a dependency for token-stats persistence. The orchestrator calls Exqlite.Sqlite3NIF.open/3 in init/1 via Stats.start_run/2.
Problem: mix escript.build bundles BEAM modules but does NOT bundle priv/ directories of dependencies, where NIF shared libraries (.so / .dylib) live. At runtime the escript loads Exqlite.Sqlite3NIF (the BEAM stub) but fails to dlopen the underlying SQLite library, raising :undef on the very first call. Symphony refuses to boot.
Failed to start Symphony ... {:undef, [{Exqlite.Sqlite3NIF, :open, ["/Users/.../.local/share/symphony/stats.db", 6], []}, ...]}
This regression is masked when developing via mix run or iex -S mix because Mix sets up the full project context including NIF priv/ paths. The escript-built binary that operators actually run is broken.
Fix options
- Switch from escript to
mix release (recommended). Releases bundle dependency priv/ directories and erts. Update make build / docs / install path.
- Configure
escript_opts to embed exqlite/priv/sqlite3_nif.so (escript doesn't natively support this; would need a custom :embed_extra_applications workaround or a post-build copy step).
- Make stats persistence soft-fail when the NIF is unavailable: wrap
Stats.start_run/2 in try/rescue, log a warning, set stats_run_id: nil, and degrade gracefully. Token totals would not persist across restarts on broken builds but Symphony would still boot.
Recommend a combination: (1) for the proper packaging path going forward, plus (3) for defense-in-depth so future NIF deps don't repeat this failure mode.
Reproduction
cd elixir && mix deps.get && mix escript.build
./bin/symphony --i-understand-that-this-will-be-running-without-the-usual-guardrails
# Failed to start: :undef on Exqlite.Sqlite3NIF.open
Workaround (operators)
Run via mix instead of the prebuilt escript:
cd elixir && mix run --no-halt -e 'SymphonyElixir.CLI.main(["--i-understand-that-this-will-be-running-without-the-usual-guardrails", "/path/to/WORKFLOW.md"])'
Why now
Surfaced today after PR #34 landed and operators rebuilt the escript to pick up the new feature set. Several other NIF-using libs (e.g. lazy_html) are already in deps; PR #34 was the first to call one at boot.
PR #34 added
exqlite(SQLite NIF) as a dependency for token-stats persistence. The orchestrator callsExqlite.Sqlite3NIF.open/3ininit/1viaStats.start_run/2.Problem:
mix escript.buildbundles BEAM modules but does NOT bundlepriv/directories of dependencies, where NIF shared libraries (.so/.dylib) live. At runtime the escript loadsExqlite.Sqlite3NIF(the BEAM stub) but fails to dlopen the underlying SQLite library, raising:undefon the very first call. Symphony refuses to boot.This regression is masked when developing via
mix runoriex -S mixbecause Mix sets up the full project context including NIF priv/ paths. The escript-built binary that operators actually run is broken.Fix options
mix release(recommended). Releases bundle dependencypriv/directories and erts. Updatemake build/ docs / install path.escript_optsto embedexqlite/priv/sqlite3_nif.so(escript doesn't natively support this; would need a custom:embed_extra_applicationsworkaround or a post-build copy step).Stats.start_run/2in try/rescue, log a warning, setstats_run_id: nil, and degrade gracefully. Token totals would not persist across restarts on broken builds but Symphony would still boot.Recommend a combination: (1) for the proper packaging path going forward, plus (3) for defense-in-depth so future NIF deps don't repeat this failure mode.
Reproduction
Workaround (operators)
Run via mix instead of the prebuilt escript:
Why now
Surfaced today after PR #34 landed and operators rebuilt the escript to pick up the new feature set. Several other NIF-using libs (e.g.
lazy_html) are already in deps; PR #34 was the first to call one at boot.