Skip to content

fix(daemon): fail fast when PKC RPC port is already in use#47

Merged
Rinse12 merged 4 commits into
masterfrom
fix/daemon-fail-fast-on-rpc-port-conflict
May 21, 2026
Merged

fix(daemon): fail fast when PKC RPC port is already in use#47
Rinse12 merged 4 commits into
masterfrom
fix/daemon-fail-fast-on-rpc-port-conflict

Conversation

@Rinse12
Copy link
Copy Markdown
Member

@Rinse12 Rinse12 commented May 21, 2026

Summary

Closes #46. bitsocial daemon previously silent-piggybacked on an existing daemon when its RPC port (default ws://localhost:9138) was taken — connecting as a client and then idling: no kubo, no challenge loading, no web UI, keepalive gated off. Users got a second "daemon" that did nothing useful. Now it exits non-zero with a message pointing at --pkcRpcUrl on the other commands (which is the real escape hatch — BaseCommand already exposes it for community list, etc.).

  • Adds an early tcp-port-used check in daemon.ts run() after flag validation and before any setup work.
  • Removes the connect-as-client branch in createOrConnectRpc, the usingDifferentProcessRpc state, the keepKuboUp guard, the tick deps field, and the call site.
  • Drops the bitsocial daemon (relying on PKC RPC started by another process) describe block whose two tests asserted the removed behavior; adds fails when PKC RPC port is already in use to the existing port-validation describe.

Test plan

  • npm run build && npm run build:test passes
  • npm run test:cli — 26 files / 206 tests passing
  • Manual smoke: start bitsocial daemon in one terminal; second bitsocial daemon in another must exit non-zero with the new message; bitsocial community list --pkcRpcUrl ws://localhost:9138 from a third terminal still works against the first daemon

Summary by CodeRabbit

  • Bug Fixes

    • Daemon now checks the configured PKC RPC port at startup and fails fast with a clear error (including the port and --pkcRpcUrl) if it's already in use, instead of attempting to attach to an existing RPC.
  • Refactor

    • Improved child-process startup handling to capture error output more reliably.
  • Tests

    • Added tests for port-availability failure and for child-output gathering; removed legacy tests for attaching to externally started RPC processes.

Review Change Stack

Previously the daemon silently switched into client mode when it
detected another process on the RPC port (default ws://localhost:9138),
then idled: it skipped kubo startup, never loaded challenge packages,
never started its own daemon server, and the keepalive tick was gated
off — so the second process did nothing useful while pretending to.

Now the daemon checks the port up front and exits non-zero with a
message that names the port and points the user at --pkcRpcUrl on the
other commands (which already exists on BaseCommand and is the right
escape hatch for talking to a running daemon).

Removes the usingDifferentProcessRpc state and the connect-as-client
branch in createOrConnectRpc; simplifies runKeepKuboUpTick deps; drops
the two tests that asserted the old silent-takeover and adds a new
"fails when PKC RPC port is already in use" integration test.

Closes #46
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9030dd9-8148-4c89-8bfd-883bda4c017f

📥 Commits

Reviewing files that changed from the base of the PR and between 5094658 and f7c7d1d.

📒 Files selected for processing (2)
  • src/ipfs/startIpfs.ts
  • test/kubo/gather-child-output.test.ts

📝 Walkthrough

Walkthrough

Daemon startup now fails fast if the configured PKC RPC port is occupied and removes the external-RPC attach branch and its flag; RPC creation and keepalive logic were simplified. Separately, IPFS child-process output handling was refactored with a new helper and tests for race/success cases.

Changes

Daemon Port Conflict Fail-Fast

Layer / File(s) Summary
Port availability check and early daemon abort
src/cli/commands/daemon.ts
Daemon.run now checks the PKC RPC port before initialization and throws a user-facing error if the port is already occupied.
Keep-alive tick interface and logic simplification
src/cli/commands/daemon.ts
KeepKuboUpTickDeps drops usingDifferentProcessRpc. runKeepKuboUpTick and keepKuboUp gating now depend only on kuboRpcClientsOptions, PKC port state, and Kubo process state; periodic invocations omit the removed field.
RPC creation port handling
src/cli/commands/daemon.ts
createOrConnectRpc re-checks the PKC RPC port for TOCTOU and throws when the port is taken, removing the attach-to-other-process branch.
Flag cleanup
src/cli/commands/daemon.ts
Removal of the old reset/management of usingDifferentProcessRpc after RPC startup.
Port conflict test coverage and keep-alive test updates
test/cli/daemon.test.ts, test/cli/keep-kubo-up-tick.test.ts
Add integration test that occupies the PKC RPC port and asserts daemon exits non-zero with "PKC RPC port is already in use" and related details. Update keep-kubo-up-tick tests to stop providing usingDifferentProcessRpc in options.

IPFS child-process output aggregation

Layer / File(s) Summary
_gatherChildOutput helper and spawn wiring
src/ipfs/startIpfs.ts
Add exported _gatherChildOutput that aggregates stderr, logs streams/events, and resolves/rejects on close; delegate _spawnAsync to this helper.
Tests for _gatherChildOutput
test/kubo/gather-child-output.test.ts
Add tests that simulate exit before stderr data (expect reject with stderr) and a successful close with code 0 (expect resolve to null).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #48: Matches changes in src/ipfs/startIpfs.ts — both add _gatherChildOutput/_spawnAsync adjustments to fix child-process exit/stderr ordering.

Poem

🐇 I peeked at ports before I hopped,

Found one taken — loudly stopped.
No sneaky attach, no silent run,
I gather stderr till the work is done. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing fail-fast behavior when the PKC RPC port is already in use.
Linked Issues check ✅ Passed All objectives from issue #46 are addressed: fail-fast on port conflict with clear error, early port check, removal of connect-as-client branch and usingDifferentProcessRpc state, simplified dependencies, updated tests, and new integration test.
Out of Scope Changes check ✅ Passed All changes are directly related to the PR objectives; the stderr-draining fix in startIpfs.ts is a necessary improvement supporting the test stability mentioned in the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/daemon-fail-fast-on-rpc-port-conflict

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/commands/daemon.ts`:
- Around line 434-436: The current check using tcpPortUsed.check(...) silently
returns when the RPC port is already taken, which can leave the daemon running
without its RPC/web UI; change the behavior in the port-taken branch inside the
daemon startup flow to throw a startup error instead of returning. Locate the
block that calls tcpPortUsed.check(Number(pkcRpcUrl.port), pkcRpcUrl.hostname)
and replace the early return with throwing a descriptive Error (include
pkcRpcUrl or its port/hostname in the message) so the startup fails fast when
the RPC port is occupied.
- Around line 286-294: The pre-flight port conflict check uses
tcpPortUsed.check(Number(pkcRpcUrl.port), pkcRpcUrl.hostname) which can miss
wildcard hostnames like 0.0.0.0/::; before calling tcpPortUsed.check (and
likewise before the reconnect check later) normalize the hostname via the same
toConnectableHostname() helper (or equivalent) used on the Kubo path so you pass
a connectable host instead of raw pkcRpcUrl.hostname; update the calls that
reference pkcRpcUrl.hostname to use the normalizedHost variable (keeping the
rest of the error message unchanged), and re-run npm run build && npm run
build:test after making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5cb4736-00a3-4403-98fd-6fb3032762aa

📥 Commits

Reviewing files that changed from the base of the PR and between 353a949 and bfe3d5c.

📒 Files selected for processing (3)
  • src/cli/commands/daemon.ts
  • test/cli/daemon.test.ts
  • test/cli/keep-kubo-up-tick.test.ts
💤 Files with no reviewable changes (1)
  • test/cli/keep-kubo-up-tick.test.ts

Comment thread src/cli/commands/daemon.ts Outdated
Comment thread src/cli/commands/daemon.ts Outdated
Rinse12 added 3 commits May 21, 2026 06:19
Same darwin EINVAL gotcha that toConnectableHostname() already guards on
the kubo path: a user passing --pkcRpcUrl ws://0.0.0.0:9138 would feed
0.0.0.0 straight to tcpPortUsed.check, which rejects it on macOS. Wrap
the hostname in toConnectableHostname at all three RPC check sites —
the fail-fast pre-check, createOrConnectRpc, and runKeepKuboUpTick.
createOrConnectRpc had an early `if (isRpcPortTaken) return` meant to be
defensive. But startedOwnRpc already short-circuits every tick call once
our server is up, so that branch is only reachable during initial
startup in a TOCTOU race where the port becomes occupied between the
pre-flight fail-fast check and the actual bind. Today the daemon
silently continues with kubo running but no RPC server and no web UI;
throw instead so startup fails fast with the port in the message.
Pulled the Promise wiring out of _spawnAsync into an exported
_gatherChildOutput so the race is unit-testable. Listen on 'close' (not
'exit'): Node guarantees 'close' fires after all stdio streams have
closed, so every stderr 'data' event has been delivered by the time we
build the rejection Error.

Why this matters: a fast-exiting child can deliver its exit signal
before its stderr drains. With the old 'exit' listener, _spawnAsync
rejected with errorMessage="". In startKuboNode the empty message
defeated the `error.message.includes("ipfs configuration file already
exists!")` suppression, turned a benign already-initialised repo into
"Failed to call ipfs init" — and because startKuboNode wraps an async
executor in `new Promise(...)`, that throw became an unhandledRejection
instead of propagating, hanging keepKuboUp() and letting the daemon
exit silently with code 0. That's the macos-latest CI failure on PR #47.

The regression test in test/kubo/gather-child-output.test.ts feeds a
fake EventEmitter the macOS event ordering (exit → data → close) and
asserts the rejection captures the stderr text. Cross-platform
deterministic, fails on the old 'exit' listener, passes on 'close'.
@Rinse12 Rinse12 merged commit f2e95aa into master May 21, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bitsocial daemon silently piggybacks on existing daemon on port conflict instead of failing fast

1 participant