feat(web): add PITCHFORK_WEB_PATH support for reverse proxy path prefixes#244
feat(web): add PITCHFORK_WEB_PATH support for reverse proxy path prefixes#244
Conversation
…ixes Allow serving the web UI under a path prefix (e.g., /ps) for use with reverse proxies like `tailscale serve --set-path=/ps 9001`. Adds --web-path CLI flag / PITCHFORK_WEB_PATH env var. Uses Axum's .nest() to mount all routes under the prefix, adds root redirect, and prefixes all hardcoded paths in HTML output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the web UI's flexibility by enabling it to operate seamlessly behind a reverse proxy or in environments where it needs to be served from a sub-path. By introducing a configurable web path prefix, the application can now correctly handle all internal and external links, HTMX requests, and SSE streams, adapting its routing and HTML output to the specified base path. This change improves deployment options and integration with existing infrastructure without altering core functionality. Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request effectively adds support for serving the web UI under a path prefix. The changes are comprehensive, touching the CLI, supervisor, web server, and all web routes to correctly handle the new web-path configuration. The use of Axum's .nest() is appropriate for this task.
My main feedback is focused on improving maintainability by reducing code duplication that has been highlighted by these changes. Specifically, the daemon_row and base_html functions are duplicated across multiple route files. Consolidating these into shared components would make the codebase easier to manage in the long run. Please see my detailed comments for suggestions.
There was a problem hiding this comment.
Pull request overview
This PR adds support for serving the Pitchfork web UI under a configurable path prefix to enable deployment behind reverse proxies with path-based routing. The implementation uses Axum's .nest() functionality to mount all routes under an optional prefix (e.g., /ps/), with automatic redirection from the root path when a prefix is configured.
Changes:
- Added
PITCHFORK_WEB_PATHenvironment variable and--web-pathCLI flag for configuring the path prefix - Implemented path prefix handling using Axum's router nesting with a redirect from root to the prefixed path
- Updated all hardcoded paths in HTML templates and JavaScript code across all route handlers to use the configurable base path
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/web/mod.rs | Adds BASE_PATH global and helper functions for path normalization and retrieval |
| src/web/server.rs | Implements router nesting with base path prefix and root redirect |
| src/web/routes/index.rs | Updates all hardcoded paths in dashboard to use bp() helper |
| src/web/routes/logs.rs | Updates all hardcoded paths in logs pages to use bp() helper |
| src/web/routes/daemons.rs | Updates all hardcoded paths in daemon management to use bp() helper |
| src/web/routes/config.rs | Updates all hardcoded paths in config editor to use bp() helper |
| src/supervisor/mod.rs | Threads web_path parameter to web server startup |
| src/cli/supervisor/run.rs | Adds web_path CLI argument and passes to supervisor |
| pitchfork.usage.kdl | Adds web-path flag specification |
| docs/cli/supervisor/run.md | Documents the new --web-path flag |
| docs/cli/commands.json | Adds web-path flag to generated CLI documentation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/web/server.rs
Outdated
| let app = Router::new() | ||
| pub async fn serve(port: u16, web_path: Option<String>) -> Result<()> { | ||
| let base_path = super::normalize_base_path(web_path.as_deref()); | ||
| let _ = super::BASE_PATH.set(base_path.clone()); |
There was a problem hiding this comment.
The result of BASE_PATH.set() is being silently ignored. If serve() is called multiple times (e.g., in tests or if the supervisor restarts), the second call will fail to set the BASE_PATH but this will be silently ignored, potentially causing bp() to return a stale or incorrect value. Consider using .expect() with a clear message, or document that serve() must only be called once per process.
| let _ = super::BASE_PATH.set(base_path.clone()); | |
| super::BASE_PATH | |
| .set(base_path.clone()) | |
| .expect("BASE_PATH was already set; serve() must only be called once per process"); |
src/web/mod.rs
Outdated
| pub(crate) fn normalize_base_path(path: Option<&str>) -> String { | ||
| match path { | ||
| None => String::new(), | ||
| Some(p) => { | ||
| let trimmed = p.trim().trim_matches('/'); | ||
| if trimmed.is_empty() { | ||
| String::new() | ||
| } else { | ||
| format!("/{trimmed}") | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The normalize_base_path function doesn't validate that the path contains only valid URL path characters. User input like "ps/../admin" or "ps//test" or paths with special characters could create invalid or unexpected routes. Consider validating that the normalized path matches a pattern like /^/[a-zA-Z0-9_-]+$/ to ensure it's a simple, safe path segment.
- Use .expect() on BASE_PATH.set() instead of silently ignoring failure - Validate web path contains only safe characters (alphanumeric, hyphens, underscores) to prevent path traversal or invalid routes - Use temporary redirect (307) instead of permanent (308) for root redirect since the path prefix is a configurable runtime setting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
normalize_base_path now returns Result instead of using assert!. Since serve() runs inside tokio::spawn, a panic would silently abort the task and bypass the error!() handler in the supervisor. Returning an error ensures invalid PITCHFORK_WEB_PATH values are properly logged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
## 🤖 New release * `pitchfork-cli`: 1.5.0 -> 1.6.0 <details><summary><i><b>Changelog</b></i></summary><p> <blockquote> ## [1.6.0](v1.5.0...v1.6.0) - 2026-02-21 ### Added - *(web)* add PITCHFORK_WEB_PATH support for reverse proxy path prefixes ([#244](#244)) - add daemon lifecycle hooks and retry count env vars ([#245](#245)) ### Fixed - pass cwd to ready_cmd spawning ([#243](#243)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Release-only version/documentation updates; no functional code changes are included in this diff. > > **Overview** > Prepares the `pitchfork-cli` **v1.6.0** release by bumping the crate/version metadata from `1.5.0` to `1.6.0` (including `Cargo.toml`, `Cargo.lock`, and generated CLI docs/spec files). > > Updates `CHANGELOG.md` with the new `1.6.0` entry summarizing the release’s added features and the `ready_cmd` working-directory fix. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e00649e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Summary
--web-pathCLI flag /PITCHFORK_WEB_PATHenv var to serve the web UI under a path prefix (e.g.,/ps).nest()to mount all routes under the prefix, with a root/→/{prefix}/redirecthref=,hx-get=,hx-post=,src=,EventSource,window.location.href) in HTML output across all route filesCloses #241
Usage
Test plan
cargo buildsucceedscargo nextest runpasses (no regressions)PITCHFORK_WEB_PATH=ps— all pages, links, HTMX, SSE, static assets work under/ps//redirects to/ps/when prefix is set🤖 Generated with Claude Code
Note
Medium Risk
Touches core web routing and many hardcoded URL strings; mis-normalization or missed prefixes could break navigation/HTMX/SSE or static asset loading, though scope is limited to the local web UI.
Overview
Adds support for serving the web UI under a configurable path prefix via new
--web-path/PITCHFORK_WEB_PATHoptions, plumbed frompitchfork supervisor runintoSUPERVISOR.start()and the web server.The Axum server now normalizes/validates the prefix, nests all routes under it, and redirects
/to/{prefix}/when configured; all HTML output (links, HTMX requests/polling, SSEEventSource, and static asset URLs) is updated to respect the base path. CLI usage specs/docs are updated accordingly.Written by Cursor Bugbot for commit 823b0f5. This will update automatically on new commits. Configure here.