Skip to content

fix: finish Rust port-base migration (17000 → 22000) across remaining call sites#47

Closed
gavin5011 wants to merge 7 commits into
Ataraxy-Labs:mainfrom
gavin5011:fix/rust-port-base-22000
Closed

fix: finish Rust port-base migration (17000 → 22000) across remaining call sites#47
gavin5011 wants to merge 7 commits into
Ataraxy-Labs:mainfrom
gavin5011:fix/rust-port-base-22000

Conversation

@gavin5011

Copy link
Copy Markdown

Summary

Commit 89168a3 ("chore: remove TypeScript fallback, Rust-only runtime") pinned integrations/tmux-plugin/scripts/server-common.sh to PORT_BASE=22000 and removed the OPENSESSIONS_RUST opt-in flag. Several call sites were missed: the TUI sidebar binary, the shared runtime helpers, the Amp/Pi TS integrations, tests, and docs all kept the legacy 17000 base.

After a fresh install, prefix + o → s silently fails — the server binds 22000 + server_key, but the spawned sidebar pane connects to ws://localhost:17000 + server_key, gets Connection refused, and dies before the user sees anything.

This PR finishes the migration end-to-end and hardens the env-parsing edges to keep client and server on the same port.

How I noticed

Fresh TPM install on macOS, cargo build --release succeeds, but prefix + o → s produces no visible output. Debug log shows the gap:

toggle_sidebar: spawning in session=1 window=@1 width=26
[sidebar] starting: connecting to ws://127.0.0.1:36916/ ...

lsof confirms the server is on 41916 (= 22000 + 19916 for socket /private/tmp/tmux-501/default). The TUI was reading the legacy 17000 base from apps/tui-rs/src/runtime_config.rs:20, so its WebSocket connection went to the wrong port and the pane died instantly.

A repo-wide grep for 17_000 / 17000 surfaced seven sites still on the old base.

Changes

Runtime — root cause

  • apps/tui-rs/src/runtime_config.rs: 17_000 + key(SERVER_PORT_BASE + u32::from(key)) as u16 via u16::try_from(...).unwrap_or_else(...) so out-of-range keys fall back to DEFAULT_SERVER_PORT and log to stderr.
  • packages/runtime-rs/src/shared.rs: resolve_server_port default base → SERVER_PORT_BASE = 22_000; resolve_server_settings drops the dead OPENSESSIONS_RUST branch and pins the same constant; resolve_server_port_with_base enforces the documented base + key <= u16::MAX precondition with base.checked_add(key).and_then(|sum| u16::try_from(sum).ok()).unwrap_or_else(...). The doc comment is rewritten to drop the dead "OPENSESSIONS_RUST + TS bun coexist" narrative.

Integrations

  • integrations/amp/opensessions.ts and integrations/pi-extension/opensessions-runtime.ts: 17000 → 22000 on both the OPENSESSIONS_SERVER_KEY and TMUX-hash branches.
  • Strict env parsing via const DECIMAL_INTEGER_RE = /^\+?[0-9]+$/. Number.parseInt is too lenient ("123abc" → 123, "0.5" → 0); Number() is too permissive ("1e3", "0x10" coerce to integers). The regex matches what Rust's u32::from_str / u16::from_str accept (decimal ASCII digits + optional leading +; empirically verified on rustc 1.96.0 that "+8123".parse::<u16>() returns Ok(8123)).
  • On invalid explicit OPENSESSIONS_SERVER_KEY, fall back to DEFAULT_SERVER_PORT rather than the TMUX-derived port, mirroring the Rust server's behavior on the same input so client and server resolve to the same port.

Tests

  • apps/tui-rs/tests/{cli,protocol}.rs: 36916 → 41916; protocol.rs adds overflow fallback cases (key=50_000 and u16::MAXDEFAULT_SERVER_PORT).
  • packages/runtime-rs/tests/config_and_shared.rs: 36916 → 41916, 25011 → 30011; added overflow fallback cases (server_key="50000", "4294967295"); renamed three tests whose names referred to the dead rust_base / ts_port_base distinction; merged the redundant set/unset env-var pair into one test that asserts equality.

Docs / comments

  • docs/ratatui-migration/{04-protocol-and-types,11-config-and-persistence}.md: 17000 → 22000 in examples.
  • Test comment block updated to describe the historical TS bun setup in past tense.

Test plan

  • cargo test --release — all test binaries pass.
  • Manual end-to-end: fresh build → restart server → press prefix + o → s → sidebar panes spawn in every active window, server listens on 22000 + server_key, TUI connects to the same port, /tmp/opensessions-err.log stays empty across multiple toggles.

Deferred to follow-up

  • resolve_server_port_with_base is now only called with SERVER_PORT_BASE in tree, so base: u32 is effectively dead configuration surface. Kept public — downgrading visibility is a separate API change.
  • SERVER_PORT_BASE is duplicated in apps/tui-rs/src/runtime_config.rs and packages/runtime-rs/src/shared.rs because tui-rs does not depend on runtime-rs. Cross-reference comments document the in-sync invariant; lifting the const into a shared crate (sidebar-protocol-rs is the natural home, but runtime-rs would need a new dep on it) is out of scope here.
  • The Amp and Pi TS resolvers have no in-tree test infrastructure. Adding TS test coverage is out of scope for this migration fix.
  • OPENSESSIONS_PORT with values outside (0, 65535] silently falls through to the SERVER_KEY / TMUX branches on both Rust and TS. Surfacing that as a hard fallback to DEFAULT_SERVER_PORT matches the SERVER_KEY arm but changes existing behavior on both sides — proposed as a separate follow-up.

gavin5011 added 7 commits June 2, 2026 15:42
Commit 89168a3 removed OPENSESSIONS_RUST opt-in and pinned
server-common.sh to PORT_BASE=22000, but left two callers on the
legacy 17000 base:

- apps/tui-rs/src/runtime_config.rs:20 hardcoded 17_000 + key
- packages/runtime-rs/src/shared.rs::resolve_server_settings
  still branched on OPENSESSIONS_RUST=1 and defaulted to 17_000
  (also resolve_server_port had 17_000 as base default)

Result: the TUI sidebar binary connects to ws://localhost:(17000+key)
while the server listens on (22000+key), so spawned sidebar panes
die immediately with 'Connection refused' and the user sees nothing
on prefix+o>s.

This commit aligns all three call sites on 22000:
- TUI runtime_config.rs: 22_000 + key
- runtime-rs shared.rs resolve_server_port default: 22_000
- runtime-rs shared.rs resolve_server_settings: drop OPENSESSIONS_RUST
  branch, pin base = 22_000

Tests in packages/runtime-rs/tests/config_and_shared.rs still hold
the old 17000-derived port assertions and need to be updated in a
follow-up before this can land upstream.
Commit 4b862c4 finished the migration for the Rust server/TUI runtime
helpers, but the rest of the tree was still on the legacy 17000 base.
Codex+Claude branch-review surfaced the gaps:

- TS integrations (Amp / Pi extension) compute the port from
  17000+server_key and post agent events to the wrong port now that
  the server binds 22000+server_key.
- Test assertions in apps/tui-rs/tests/{cli,protocol}.rs and
  packages/runtime-rs/tests/config_and_shared.rs still expect
  36916 / 25011, which only hold with the old base.
- The 'OPENSESSIONS_RUST coexist with TS bun' doc comment on
  resolve_server_port_with_base no longer matches the implementation
  (no env-var branch left).
- 22_000 was duplicated as a magic number across resolve_server_port
  and resolve_server_settings without a SSoT constant.
- apps/tui-rs/src/runtime_config.rs computed '22_000 + key' in u16,
  losing the 5000-wide overflow margin that 17_000 used to provide;
  a server key >= 43_536 would debug-panic / silently wrap.
- docs/ratatui-migration/{04-protocol-and-types,11-config-and-persistence}.md
  still showed 17000-based examples.

Changes:
- integrations/amp/opensessions.ts: 17000 -> 22000 (both call sites)
- integrations/pi-extension/opensessions-runtime.ts: same
- packages/runtime-rs/src/shared.rs: introduce
  pub const SERVER_PORT_BASE: u32 = 22_000 and use it from
  resolve_server_port + resolve_server_settings; rewrite the doc
  comment for resolve_server_port_with_base to drop the dead
  OPENSESSIONS_RUST + TS bun coexist narrative and document the
  base+server_key <= u16::MAX precondition instead.
- apps/tui-rs/src/runtime_config.rs: mirror the SERVER_PORT_BASE
  constant and compute (SERVER_PORT_BASE + key as u32) as u16 so
  the arithmetic stays in u32 and never debug-panics.
- apps/tui-rs/tests/cli.rs, apps/tui-rs/tests/protocol.rs,
  packages/runtime-rs/tests/config_and_shared.rs: update assertions
  (36916 -> 41916, 25011 -> 30011) and refresh the test comment
  block that still described the dead TS bun coexist setup.
- docs/ratatui-migration/{04,11}: 17000 -> 22000 in examples.

cargo test --release: all 14 test binaries pass.
…aming

Branch-review round 2 surfaced eight remaining issues on the diff
introduced by commits 4b862c4 + 85b36e4. This patch resolves the
correctness ones inline and renames the now-misleading tests.

Correctness (F2/F3 — Amp + Pi TS integrations):
  Both resolvers parsed OPENSESSIONS_SERVER_KEY without NaN guard or
  bounds check, so a non-numeric value yielded `http://127.0.0.1:NaN`
  and a key >= 43536 produced a port > 65535. The previous 17000 base
  hid the upper bound at 48535; the migration tightened it to 43535
  without adding the check. Validate parse + bounds and fall through
  to the tmux-hash branch / DEFAULT_SERVER_PORT otherwise.

Correctness (F4/F5 — Rust port arithmetic):
  Both `apps/tui-rs/src/runtime_config.rs::resolve_server_port` and
  `packages/runtime-rs/src/shared.rs::resolve_server_port_with_base`
  cast `base + key` to u16 with `as u16`, which silently truncates
  when the sum exceeds u16::MAX. The doc comment introduced in the
  previous commit documented the precondition but did not enforce it;
  in practice the same env var that breaks the TS branches reaches
  this code through resolve_server_key + parse::<u32>(). Replace the
  unchecked cast with `u16::try_from(...).ok().unwrap_or(DEFAULT_SERVER_PORT)`
  (plus checked_add on the shared.rs side, since base is u32 + key is
  u32 there) so out-of-range input falls back deterministically to
  the same default the parse-failure branch already uses.

Testing (F1/F6/F8 — stale 'rust_base' terminology in test names):
  - resolve_server_settings_uses_rust_port_base_when_opensessions_rust_set
    → resolve_server_settings_ignores_opensessions_rust_env_var
    (the env var no longer affects the port; rename matches the
    behavior the test now actually exercises). Assertion message also
    updated from "OPENSESSIONS_RUST=1 must use base 22000" to
    "Rust stack must bind base 22000".
  - resolve_server_settings_keeps_ts_port_base_when_opensessions_rust_unset
    → resolve_server_settings_uses_default_port_base_without_opensessions_rust
    (TS base no longer exists; the test actually pins the default base
    when the env var is missing).
  - resolve_server_settings_explicit_port_overrides_rust_base
    → resolve_server_settings_explicit_port_overrides_port_base
    (single port base now, no 'rust' qualifier needed).

Deferred to follow-up: F7 (resolve_server_port_with_base now always
called with SERVER_PORT_BASE, so the `base: u32` parameter is dead
configuration surface — kept public for now since downgrading visibility
is an API change separate from the migration fix).

cargo test --release: all 15 test binaries pass.
…r overflow paths

Branch-review round 3 surfaced two more issues caused by the round-2
patch:

Correctness — endpoint divergence on invalid OPENSESSIONS_SERVER_KEY:
  The TS Amp/Pi resolvers fell through to the TMUX-hash branch when
  the explicit-key parse/range check failed, while the Rust server
  (packages/runtime-rs/src/shared.rs::resolve_server_port_with_base)
  falls back to DEFAULT_SERVER_PORT on the same input. With a bad
  OPENSESSIONS_SERVER_KEY set, client posts would go to the TMUX
  port while the server listens on DEFAULT_SERVER_PORT — a silent
  drop. Make both TS resolvers return DEFAULT_SERVER_PORT in the
  same case so client and server agree.

Testing — overflow fallback coverage:
  The round-2 try_from / checked_add changes added a deterministic
  fallback for keys that push 22000+key above u16::MAX, but no test
  exercised the fallback. Add cases at key=50_000 (Some(u16) sum
  72_000 > u16::MAX) and key=u16::MAX / "4294967295" (also covers
  the checked_add(u32) path on the runtime-rs side).

TS-side test coverage for the Amp/Pi resolvers is deferred — the
integrations have no existing test infrastructure in-tree and adding
it is out of scope for this migration fix; tracked as follow-up.

cargo test --release: all 15+ test binaries still pass.
… merge

R4 (Codex+Claude cross-validated) surfaced two high-severity correctness
issues and four supporting items on the round-3 commit. This patch
addresses all six on the diff and leaves a documented duplication
warning where consolidation would change crate dependencies.

Correctness — F1/F2 (high):
  `Number.parseInt("123abc", 10)` returns 123 in JS, not NaN, so the
  Amp + Pi resolvers were silently sending events to port 22123 while
  the Rust server (which uses strict `parse::<u32>()`) was listening
  on DEFAULT_SERVER_PORT 7391. Same for "0.5" → JS 0 vs Rust Err.
  The previous round's mirror comment was wrong about the parity it
  claimed. Switch to `Number(explicitKey)` + `Number.isInteger` so
  the TS path Err's on the same inputs as Rust's u32 parse.

Correctness — F4 (medium):
  Both `resolve_server_port_with_base` (runtime-rs) and TUI
  `resolve_server_port` silently fell back to DEFAULT_SERVER_PORT
  when SERVER_PORT_BASE + key overflowed u16. Add a single
  `eprintln!` on that branch identifying the inputs so operators see
  why the client/server cannot agree on a port. Parse-failure stays
  silent (it has a clearer cause and tends to fire on user typos).

Doc — F5 (low):
  `resolve_server_port_with_base` doc still claimed "callers must
  ensure base + server_key <= u16::MAX" after round 3 made the
  implementation enforce a fallback. Rewrite to describe the actual
  contract (fallback + stderr warn).

Testing — F6 (medium, downgrade in R4):
  Round 3 renamed the test to
  `resolve_server_settings_ignores_opensessions_rust_env_var` but
  the body only exercised the OPENSESSIONS_RUST=1 case. The
  "ignores" claim was unverified. Merge the env-set and env-unset
  cases into the same test and assert their ports are equal — that
  is the property the name promises. The standalone
  `_uses_default_port_base_without_opensessions_rust` test is now
  redundant and removed.

Testing — F7 (low):
  `resolve_server_settings_explicit_port_overrides_port_base` no
  longer needs to set OPENSESSIONS_RUST=1 since the env var has no
  effect on the port. Drop the stale setup so the test exercises
  only the property it names.

Convention — F8 (low, deferred):
  `SERVER_PORT_BASE` is duplicated in apps/tui-rs/src/runtime_config.rs
  and packages/runtime-rs/src/shared.rs because tui-rs does not depend
  on runtime-rs. Adding the dep is broader scope than this migration
  fix. Document the duplication and the in-sync invariant on both
  sides instead — moving the const to a shared crate
  (sidebar-protocol-rs is a natural home) is left as follow-up.

Codex also reported (F3, medium → downgrade in R4): the new TUI
overflow fallback is unreachable through the only existing call site
(hash is bounded by % 20_000). Kept the defensive try_from + eprintln
anyway — `OPENSESSIONS_SERVER_KEY` is the unbounded path that motivates
it.

cargo test --release: 211 tests pass (one redundant test removed; the
merged env-var test now asserts equality across set/unset paths).
R5 (Codex+Claude) found that the round-4 fix using `Number()` +
`Number.isInteger` was still too lenient. JS coerces `Number("1.0")`
→ 1, `Number("1e3")` → 1000, `Number("0x10")` → 16, all of which
pass `Number.isInteger`, while Rust's `parse::<u32>()` rejects them
all. The same lenience was present on the `OPENSESSIONS_PORT` branch
(`Number.parseInt` accepts trailing garbage and decimal prefixes),
which R5 surfaced as a second pair of medium findings.

Replace both `OPENSESSIONS_PORT` and `OPENSESSIONS_SERVER_KEY`
parsing with a `/^[0-9]+$/` regex gate before `Number()` in both
Amp and Pi extensions. This matches the inputs accepted by Rust's
`u32::from_str` / `u16::from_str` (decimal ASCII digits only,
leading zeros allowed, no signs / decimals / scientific / hex) so
the client and the Rust server agree on every reachable input.

Deferred (R5):
  F3/F4 (medium, testing): the Amp and Pi resolveServerPort lack
  any in-tree integration test coverage. Adding test infrastructure
  for TS integrations is out of scope for the migration fix.
  F5 (medium, correctness): claude-only false alarm — the u32
  expression `SERVER_PORT_BASE + u32::from(key)` with
  SERVER_PORT_BASE=22_000 and key: u16 cannot overflow u32 (max
  22_000 + 65_535 = 87_535).
  F8-F11 (low): cosmetic — port-ceiling re-check, stderr message
  detail, const duplication (also F10 from earlier), redundant
  test assertion.

cargo test --release: still passes.
…parity with Rust)

R6 (Codex) flagged that the R5 regex  was stricter than
Rust on one input class: `u16::from_str` and `u32::from_str` accept
an optional leading `+` (`+8123` parses to 8123 in Rust), but the
TS regex rejected those, so a user setting OPENSESSIONS_PORT=+8123
would have the Rust server bind 8123 while the TS client fell back
to TMUX-derived or DEFAULT_SERVER_PORT — exactly the silent endpoint
mismatch this PR is supposed to eliminate.

Note: Claude's Stage B refuted the finding based on incorrect Rust
knowledge (claimed `u32::from_str` rejects `+`). Verified empirically
on rustc 1.96.0: `"+8123".parse::<u16>()` returns Ok(8123), so the
Codex side was correct. Applying the fix to both Amp and Pi.

Loosen the regex to `/^\\+?[0-9]+$/` and document the empirical
verification next to it in both files.

Deferred from R6 (all CONFIRMED or REFUTED at low / medium severity):
  F3 (medium): u32 sum overflow on tui-rs side — false alarm
  (key: u16 is structurally bounded, 22_000 + 65_535 cannot
  overflow u32).
  F4, F8, F10, F11 (low): cosmetic.
  F5, F6 (medium): tmux hash branch overflow — hashServerKey is
  bounded `% 20_000` so 22_000 + 19_999 = 41_999 cannot overflow
  u16; the explicit-key branch already has bounds check.
  F9 (medium): SERVER_PORT_BASE still duplicated across two
  crates. The cross-reference comments added in R4 document
  the in-sync invariant; lifting the const into a shared crate
  (sidebar-protocol-rs is the natural home, but runtime-rs would
  need to gain a new dep on it) is out of scope for this fix.

cargo test --release: still passes.

@inspect-review inspect-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

inspect review

Triage: 20 entities analyzed | 0 critical, 0 high, 15 medium, 5 low
Verdict: standard_review

Findings (1)

  1. [low] In packages/runtime-rs/src/shared.rs, resolve_server_port_with_base has truncated error message. The string literal 'DEFAULT_SE' is incomplete and will cause a compilation error.

Reviewed by inspect | Entity-level triage found 0 high-risk changes

@gavin5011 gavin5011 closed this Jul 1, 2026
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.

1 participant