Skip to content

fix: genie api dashboard proxies to core port - #102

Merged
ai-hpc merged 2 commits into
GeniePod:mainfrom
andriypolanski:fix/genie-api-dashboard-proxies-to-core-port
May 18, 2026
Merged

fix: genie api dashboard proxies to core port#102
ai-hpc merged 2 commits into
GeniePod:mainfrom
andriypolanski:fix/genie-api-dashboard-proxies-to-core-port

Conversation

@andriypolanski

Copy link
Copy Markdown
Contributor

Summary

genie-api proxies actuation, memories, and runtime-contract endpoints to genie-core via raw HTTP over TCP. Those proxies always dialed 127.0.0.1:3000, ignoring [core].bind_host and [core].port in geniepod.toml. With a non-default core port, the dashboard Services row for core could still look healthy (health checks use [services.core].url) while Actuation and Memories panels failed with connection errors.

Closes #99
Related: #90 (fixed genie-ctl only)

Changes

  • Add Config::core_http_addr() in genie-common (map 0.0.0.0 / :: to 127.0.0.1 for local clients; same semantics as the former genie-ctl helper).
  • Update proxy_core_json in genie-api to connect using that address; connection errors include the dialed host:port.
  • Switch genie-ctl to the shared helper so CLI and API stay aligned.
  • Unit tests in genie-common and genie-api.

Real Behavior Proof

  • I have built and run the affected code locally (or noted why I could not).
  • I have verified the change end-to-end on Jetson hardware OR explained the equivalent verification path I used.

Environment: x86_64 Ubuntu 24.04 VM (Linux 6.17.0-23-generic), not Jetson. Equivalent path: same geniepod.toml + genie-core / genie-api stack as production, with [core].port = 3001 and llama.cpp on :8080 (dev config).

What I ran

# Build
cd /home/daniel-james/Documents/work/genie-claw
cargo build -p genie-api -p genie-core --release

# Config: dev template with core on 3001
cp deploy/config/geniepod.dev.toml /tmp/geniepod-pr99.toml
sed -i 's/port = 3000/port = 3001/' /tmp/geniepod-pr99.toml
sed -i 's|127.0.0.1:3000|127.0.0.1:3001|g' /tmp/geniepod-pr99.toml

# Run stack (same GENIEPOD_CONFIG for both)
GENIEPOD_CONFIG=/tmp/geniepod-pr99.toml ./target/release/genie-core   # background
GENIEPOD_CONFIG=/tmp/geniepod-pr99.toml ./target/release/genie-api    # background

# Health + proxy vs direct core
curl -sf http://127.0.0.1:3001/api/health | jq -r '.status,.llm'
curl -s http://127.0.0.1:3001/api/actuation/pending
curl -s http://127.0.0.1:3080/api/actuation/pending
diff <(curl -s http://127.0.0.1:3001/api/actuation/pending) \
     <(curl -s http://127.0.0.1:3080/api/actuation/pending)

curl -s -w "\nHTTP %{http_code}\n" http://127.0.0.1:3080/api/memories
curl -s -w "\nHTTP %{http_code}\n" http://127.0.0.1:3080/api/runtime/contract

# Issue repro: nothing on :3000 while core listens on :3001
curl -s -w "HTTP %{http_code}\n" http://127.0.0.1:3000/api/actuation/pending

# Unit tests
cargo test -p genie-common core_http_addr
cargo test -p genie-api proxy_core_uses_configured

What I observed

  • Build: Finished release profile for genie-common, genie-core, genie-api.
  • genie-core log: starting HTTP chat API port=3001 and genie-core HTTP server listening addr=127.0.0.1:3001.
  • genie-api log: listening addr="127.0.0.1:3080".
  • Health on configured port: curl …:3001/api/health"status":"ok", "llm":"connected".
  • Proxied routes (3080) match direct core (3001):
    • GET /api/actuation/pending{"audit_log":{...},"pending":[]} on both; diffidentical.
    • GET /api/memories[], HTTP 200 via :3080.
    • GET /api/runtime/contract → JSON with contract_hash, HTTP 200 via :3080.
  • Old bug scenario: curl …:3000/api/actuation/pending → connection failed (HTTP 000) while :3080 proxy succeeds — confirms proxies no longer assume port 3000.
  • Unit tests: core_http_addr_* and proxy_core_uses_configured_core_http_addr passed.

Test plan

  • cargo test -p genie-common core_http_addr
  • cargo test -p genie-api proxy_core_uses_configured_core_http_addr
  • cargo test -p genie-ctl
  • Manual curls above with [core].port = 3001 (x86 dev VM)
  • Optional on Jetson: open http://127.0.0.1:3080/ and confirm Actuation / Memories panels load with non-default core port

Notes for reviewers

Affected routes

  • GET /api/actuation/pending
  • GET /api/actuation/actions
  • POST /api/actuation/confirm
  • GET /api/runtime/contract
  • GET /api/memories
  • POST /api/memories/update, /delete, /reorder

@ai-hpc ai-hpc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the right architectural fix for #99. PR #92 (the genie-ctl version of this bug) added the core_addr_from_config helper locally inside genie-ctl/src/main.rs; this PR correctly identifies that the same logic was hardcoded in genie-api's proxy_core_json, promotes the helper to a public Config::core_http_addr() on genie-common::config::Config, and threads it through both crates. Net -14 lines in genie-ctl because the duplicate goes away. That's the architectural cleanup PR #92 would have done if we'd known #99 was coming.

Substance

  • Config::core_http_addr() preserves the 0.0.0.0 / ::127.0.0.1 mapping from PR #92 (local callers want loopback even when core listens on all interfaces). Empty bind_host falls back to 127.0.0.1. Tests in genie-common (core_http_addr_uses_bind_host_and_port, core_http_addr_maps_listen_all_to_loopback) pin both branches.
  • proxy_core_json now takes config: &Config as the first arg. Every caller (get_actuation_pending, get_actuation_actions, get_runtime_contract, post_actuation_confirm, get_memories, post_memory_update, post_memory_delete, post_memory_reorder) renames its _config param to config and threads it through. 8 call sites, all consistent — easy to read, easy to grep.
  • Host: header is now correct. Previously hardcoded Host: 127.0.0.1; now uses the actual host parsed from addr.rsplit_once(':'). Necessary because the dashboard could (in theory) talk to a non-loopback host, and getting the Host: header wrong breaks vhost-routing in any reverse proxy in front of genie-core.
  • Error messages include the dialed host:port (format!("{addr}: {e}")). The previous opaque "Connection refused" left operators guessing which port the proxy thought it was hitting; now they see it immediately. Tiny but real DX win.
  • core_proxy_addr_uses_configured_core_port test in genie-api sets config.core.port = 3001 and asserts config.core_http_addr() == "127.0.0.1:3001". Pins the contract.

End-to-end verification in the PR body is the strongest signal: same geniepod.toml for both daemons with port = 3001, then diff <(curl :3001/api/actuation/pending) <(curl :3080/api/actuation/pending) returns identical output. Before this PR, the second curl would have hung or errored. After, the proxy correctly routes to the configured port.

One small contributor note (not blocking)

The commit author email is andiry.polanski@outlook.com — a personal Outlook address rather than the GitHub-noreply form. That lands in git log permanently on a public AGPL repo where every clone / mirror picks it up. The fix is a one-time git config --global user.email "<numeric-id>+andriypolanski@users.noreply.github.com" (find the noreply form at https://github.com/settings/emails), then git commit --amend --reset-author + force-push.

I'm flagging this as a soft norm rather than a hard block because:

  • The commit author login (andriypolanski) matches the PR submitter — no account mismatch concern.
  • No AI co-author trailer in the commit message.
  • The work is clearly correct and addresses a real production bug.

PR #105 was the strict-block case (account mismatch + Cursor trailer + personal Gmail — three compounding issues). This is just the email; not the same severity. Maintainer's call whether to land as-is and just note for next time, or hold for the amend.

CI

statusCheckRollup had only the PR body checklist: SUCCESS entry — the cargo / clippy / test / aarch64 cross-compile / --no-default-features jobs were waiting on first-time-contributor approval. I approved both queued workflow runs (CI: 26042476432, Cross-compile: 26042476465) so the full check matrix will populate on the current head.

Interaction with PR #105

PR #105 (fix/103-configured-service-http-probes, currently with a CHANGES_REQUESTED review) also touches genie-common/src/config.rs to add [services.api] and service_http_probe(). If both merge, there's no direct conflict (different functions, separate sections), and #105 actually could have used this PR's Config::core_http_addr() helper rather than duplicating the parsing logic in its own service_http_probe. Once #102 lands, a follow-up review of #105 should suggest harmonizing those two helpers (or at least cross-referencing them in the comments).

Verdict

Approving on substance. Waiting on CI to clear green on the new head before this is mergeable.

The two prior branches (empty host vs `0.0.0.0` / `::`) both returned
`"127.0.0.1"`, which trips `clippy::if_same_then_else -D warnings` on
the workspace lib. Combine into a single OR'd condition; behavior is
byte-identical.

No-op fix to unblock CI on PR GeniePod#102.
@ai-hpc
ai-hpc merged commit 191cc32 into GeniePod:main May 18, 2026
6 checks passed
@ai-hpc

ai-hpc commented May 18, 2026

Copy link
Copy Markdown
Contributor

Merged at 191cc32451d9a1ec8906f6557822b9bf2f247e2b.
Thanks to @andriypolanski!

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.

genie-api dashboard proxies ignore [core].port — hardcode 127.0.0.1:3000

2 participants