Common problems contributors hit while working on NotifyChain, and how to resolve them.
This guide covers the contribution workflow — building, testing, linting, running services locally, Git, and CI. For other problem domains:
| Guide | Covers |
|---|---|
TROUBLESHOOTING.md |
First-time environment setup — installing Rust, Stellar CLI, Node |
DEPLOYMENT_TROUBLESHOOTING.md |
Deployment and staging failures |
| API Error Reference | HTTP error responses from the listener API |
| Git Workflow Guide | Branching, commits, and the PR process |
- Start Here — Triage
- Install and Dependency Issues
- TypeScript and Build Failures
- Test Failures
- Running the Listener Locally
- Database and Migration Issues
- Dashboard Issues
- Smart Contract (Rust) Issues
- Git and Pull Request Issues
- CI Failures
- Still Stuck?
Before diving into a specific section, three checks resolve a large share of problems.
Are you on the right Node version? CI runs Node 22. A different major version is the most common source of "works locally, fails in CI".
node --versionAre you in the right directory? Each component has its own package.json. Running npm test at the repo root does nothing useful — you must be in listener/ or dashboard/.
Is your branch current? A failure caused by a stale branch disappears after syncing:
git fetch upstream && git merge upstream/mainnpm ERR! `npm ci` can only install packages when your package.json and
npm ERR! package-lock.json are in sync.
Cause: package.json was edited without regenerating the lockfile — often by hand-editing a version, or by a merge that resolved package.json but not package-lock.json.
Fix:
cd listener # or dashboard
npm install # regenerates package-lock.jsonCommit the updated package-lock.json. Never delete it to make an error disappear — CI uses npm ci, which requires it.
Branches can carry different dependency sets. Reinstall:
cd listener
npm installIf that doesn't resolve it, clear and reinstall:
rm -rf node_modules
npm installDon't hand-resolve it. Take one side, then regenerate:
git checkout --theirs package-lock.json
npm install
git add package-lock.jsonIn this repo npm run lint and npm run typecheck are both tsc --noEmit for the listener — a lint failure is a type error.
cd listener
npm run typecheckRead the first error, not the last. TypeScript errors cascade: one bad type produces a dozen downstream complaints that vanish when the first is fixed.
Cause: Usually a domain type in listener/src/types/ was changed without updating every consumer — exactly the class of bug TypeScript is here to catch (see ADR-0004).
Fix: Update the type definition or the call site so they agree. Don't reach for as any — it silences the check and moves the failure to runtime.
npm start runs dist/, not src/. If you didn't rebuild, you're running the previous compile:
cd listener
npm run build
npm startFor development use npm run dev instead — it runs TypeScript directly through ts-node, no build step.
The dashboard lints with --max-warnings=0, so a warning fails the build:
cd dashboard
npm run lintFix the warnings. Don't raise the threshold — CI enforces zero.
Run tests from the component directory:
cd listener && npm test
cd dashboard && npm testcd listener
npm test -- src/api/events-server.test.tsFilter by test name:
npm test -- -t "deduplication"Cause: Shared state leaking between tests — a module-level cache, a database file, or a timer that outlives its test.
Fix: Reset state in beforeEach/afterEach. For the deduplicator and similar caches, construct a fresh instance per test rather than reusing a module-level singleton.
Cause: An open handle — a server, database connection, or interval that was never closed.
Fix: Close what you opened in afterEach/afterAll. To find the culprit:
cd listener
npm test -- --detectOpenHandlesDeduplication and rate-limiting logic is time-windowed. Tests that use real wall-clock time are flaky by construction.
Fix: Inject the clock rather than reading it. NotificationDeduplicator accepts a now: () => number option precisely for this — pass a controllable function instead of relying on Date.now().
If the change is intentional:
npm test -- -uReview the updated snapshots in the diff before committing. An unreviewed -u can silently bless a regression.
cd listener
cp .env.example .env # then edit
npm install
npm run devThe events API defaults to port 8787.
Find and stop the process holding it:
lsof -i :8787
kill <PID>Or run on a different port by setting EVENTS_API_PORT in listener/.env.
Work through these in order:
- Is the contract address correct? Check it in
listener/.envagainst the deployed contract. - Is the RPC endpoint reachable? A wrong or unreachable URL usually surfaces as repeated poll errors in the logs.
- Are you on the right network? A testnet contract address against a mainnet RPC returns nothing — no error, just silence.
- Have the events already been consumed? Deduplication suppresses events seen within the window. Restarting clears the in-memory cache (see ADR-0005).
A 503 means an optional subsystem isn't enabled, not that the server is broken. Scheduler not enabled on /api/schedule* is the common case — it's off by default. See Section 11 of the API Error Reference.
Check the path and method. /api/events and /api/v1/events both work; /v1/events does not. Scheduling is POST /api/schedule, not GET.
The listener uses SQLite, defaulting to ./data/notifications.db (override with DATABASE_PATH). See ADR-0003.
Cause: Two processes have the database file open — commonly a stray npm run dev from a previous session, or a DB browser you left connected.
Fix: Find and stop the other process, or point your test run at a separate DATABASE_PATH.
Check migration status:
cd listener
npm run check-migrationsApply pending migrations:
npm run migrateIf a branch added a migration that another branch doesn't have, the schema can end up in a state neither expects. Locally, the fastest fix is a clean database:
rm -f data/notifications.db
npm run migrateThis deletes all local data. Only do it in development, never against anything you need to keep.
The parent directory doesn't exist. Create it:
mkdir -p dataThe dashboard reads from the listener API. Confirm, in order:
- The listener is running —
curl http://localhost:8787/health. - The dashboard points at the right URL — check
dashboard/.envagainst the listener's actual port. - No CORS errors in the browser console — the listener sets CORS headers; a mismatch usually means the wrong origin or port.
Port already taken — stop the other process or start Vite on another port:
cd dashboard
npm run dev -- --port 5174Wallet-specific problems are covered in the Freighter Troubleshooting section of README.md.
The WebAssembly target isn't installed:
rustup target add wasm32-unknown-unknownClear stale build artifacts:
cd contract/contracts/hello-world
cargo clean
cargo testThe Stellar CLI isn't installed or isn't on PATH:
cargo install --locked stellar-cli --features optIf it installs but isn't found, ensure ~/.cargo/bin is on your PATH.
Build with the release profile and the correct target — a debug build produces a much larger artifact that may exceed limits. Check the Makefile in contract/contracts/hello-world/ for the canonical build command.
Full workflow details are in the Git Workflow Guide. The failures that come up most often:
Cause: You branched off another feature branch instead of a synced main.
Fix: Re-create the branch from an up-to-date main and move only your commits across. See Git Workflow §10.
git branch feature/my-work
git reset --hard upstream/main
git checkout feature/my-work
--harddiscards uncommitted work. Rungit statusfirst.
Someone (or you, elsewhere) pushed to that branch. Integrate before pushing:
git pull --rebase origin <branch-name>
git pushDon't force-push a branch that's under review unless a reviewer asks — it makes incremental re-review much harder.
You're pushing to upstream instead of origin. Contributors push to their fork only:
git remote -v # confirm origin is YOUR fork
git push origin <branch-name>git checkout main
git fetch upstream && git merge upstream/main
git checkout <your-branch>
git merge main
# resolve, then:
git add . && git commit && git pushResolve by understanding both sides — if upstream changed a signature you also touched, your code needs to adapt to the new one.
CI runs lint, typecheck/build, and tests per component. Run the same gates:
cd listener && npm run lint && npm test
cd ../dashboard && npm run lint && npm run build && npm testRun the checks for every component you touched. A green listener says nothing about the dashboard.
The usual causes, in order:
- Node version — CI uses Node 22.
npm installvsnpm ci— CI usesnpm ci, which installs strictly from the lockfile. If your lockfile is stale, CI sees different dependencies than you do.- Uncommitted files — a file that exists locally but was never
git added. Checkgit status. - Case-sensitive imports — macOS is case-insensitive, CI's Linux is not.
import './Foo'resolves locally and fails in CI when the file isfoo.ts. - Test ordering or timing — see Section 4.
The CI workflow is path-filtered on pull requests — it triggers on changes under listener/src/migrations/, listener/src/database/, listener/src/scripts/, and listener/package.json. A PR touching only documentation legitimately runs no jobs. That's expected, not a failure.
Before opening an issue, gather:
- What you ran — the exact command and directory.
- What happened — the full error output, not a paraphrase.
- Environment —
node --version,npm --version, and OS. - Branch state —
git statusandgit log --oneline -3. - What you already tried.
Then:
- Search existing issues first — Issue tracker. Most contributor-facing problems have been hit before.
- Comment on the issue you're working on if it's specific to that work.
- Open a new issue if the problem is reproducible and undocumented — and consider a PR adding it to this guide.
If a fix here is wrong or out of date, that's a bug in the docs. Please fix it.