feat(port-management): impl proxy for local dev#293
Conversation
|
Warning Gemini is experiencing higher than usual traffic and was unable to create the summary. Please try again in a few hours by commenting |
Greptile SummaryThis PR implements a local-dev reverse proxy for pitchfork, routing Confidence Score: 4/5Safe to merge with the two P2 items addressed; all prior security concerns have been resolved in this revision. The major prior concerns (CA key exposure via blanket chmod, world-writable IPC socket, slug routing via config files instead of state file, daemon dir ignored in routing) are all resolved. Two new P2 findings remain: is_daemon_slug_target ignores namespace causing unnecessary listeners::get_all() polling for same-named daemons in other projects, and non-deterministic routing when namespace resolution silently fails. Neither causes incorrect routing but both can cause confusing behavior in multi-project setups. src/supervisor/lifecycle.rs (is_daemon_slug_target namespace scoping) and src/proxy/server.rs (multi-match fallback warning) Important Files Changed
Sequence DiagramsequenceDiagram
participant Browser
participant Proxy as Proxy Server
participant Cache as Slug Cache (2s TTL)
participant StateFile as State File
participant Daemon as Backend Daemon
Browser->>Proxy: GET https://api.localhost:7777/path
Proxy->>Proxy: Strip port → host = api.localhost
Proxy->>Cache: cached_slug_lookup(api)
alt Cache expired
Cache->>Cache: build_slug_entries() reads global config
Cache-->>Proxy: CachedSlugEntry namespace project-a daemon_name api
else Cache hit
Cache-->>Proxy: CachedSlugEntry from memory
end
Proxy->>StateFile: lock and clone daemons
StateFile-->>Proxy: IndexMap DaemonId Daemon
Proxy->>Proxy: filter by name + namespace to active_port
alt Daemon running active_port set
Proxy->>Daemon: GET http://localhost:3000/path
Daemon-->>Proxy: 200 OK
Proxy-->>Browser: 200 OK plus x-pitchfork header
else Daemon not running or no port
Proxy-->>Browser: 502 Bad Gateway
end
Note over Daemon,StateFile: On daemon start detect_and_store_active_port polls listeners every 500 1000 2000 4000 ms and writes active_port to state file
Reviews (9): Last reviewed commit: "fix: misc" | Re-trigger Greptile |
|
@gaojunran I am still thinking about this but I think that maybe we should define the slugs only in the global pitchfork.toml which would point to project directories. I'm thinking that would be a more obvious way to configure it when later we get auto-start when trying to connect to one. Otherwise you'd need to first run the daemon in a project without auto-start to get it populated into state.toml which I think isn't obvious to users. |
@jdx I understand your thoughts. And I'm hesitating about if it's complicated or bothersome for users to define twice in two pitchfork.toml. And in a specific project, every user will need to configure once in global pitchfork.toml. What about just introducing Is that acceptable? I think that's more user friendly. And using trust to maintain a mapping from namespace to project dir is useful. Just an idea and I can also follow your idea if you don't prefer this. |
|
I would definitely prefer to avoid trust if at all possible, it's a pretty annoying thing about mise. I suppose if pitchfork is auto-launching we may need it though. |
|
Tip: Greploop — Automatically fix all review issues by running Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal. |
dcc5aca to
5317271
Compare
|
@jdx Ready for a review! |
|
I've been thinking more about the slug architecture. I think slugs should be defined only in the global config ( Proposed format# ~/.config/pitchfork/config.toml
[slugs]
api = { dir = "/home/user/my-api", daemon = "server" }
frontend = { dir = "/home/user/my-app", daemon = "dev" }
# or if daemon name matches slug:
docs = { dir = "/home/user/docs-site" } # defaults daemon = "docs"Each slug entry maps to a project directory and (optionally) a specific daemon name within that project. Why this is better than the current approach
What this means for the PR
Auto-start (future, not needed in this PR)With this design, auto-start later is straightforward: proxy gets a request for This comment was generated by Claude Code. |
|
@jdx I like this approach! This resolves all the problems. |
feat(port-management): impl url reverse proxy [autofix.ci] apply automated fixes Update src/proxy/server.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> [autofix.ci] apply automated fixes fix: misc Merge branch 'main' into feat-proxy
refactor: explicitly ask user to register slugs chore: conflict fix: misc [autofix.ci] apply automated fixes
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
## 🤖 New release * `pitchfork-cli`: 2.4.0 -> 2.5.0 <details><summary><i><b>Changelog</b></i></summary><p> <blockquote> ## [2.5.0](v2.4.0...v2.5.0) - 2026-04-10 ### Added - *(port-management)* impl proxy for local dev ([#293](#293)) ### Other - *(deps)* update rust crate indexmap to v2.14.0 ([#321](#321)) - *(deps)* update rust crate xx to v2.5.3 ([#322](#322)) - *(deps)* update rust crate tokio to v1.51.1 ([#320](#320)) - update inconsistencies in docs ([#312](#312)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > <sup>[Cursor Bugbot](https://cursor.com/bugbot) is generating a summary for commit d305742. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
No description provided.