fix(web): /c/[slug] 500 → 404 on unknown tag#10
Merged
Merged
Conversation
Route audit found /c/<unknown-slug> returning 500 (Pages Router /_error shell) instead of 404 (App Router not-found.tsx). Other dynamic routes (/post/[id], /u/[username], /projects/[slug], /office/persona/[name], /office/decision/[id]) all correctly 404 on unknown ids; /c/[slug] was the only one that 500ed. Cause: /c/[slug] is the only dynamic route that defines generateStaticParams. With the tags table empty (no submissions → no auto-tags yet on this fresh deploy), generateStaticParams returns []. Next.js 15's static-optimization heuristic then treated the route as fully prerendered with zero params, so unknown slugs missed both the static index and the App Router's dynamic-render → notFound() path. Fix: `export const dynamic = "force-dynamic"`. The page reads auth() (cookies) anyway, so it's request-scoped — force-dynamic just makes that explicit. generateStaticParams stays as a build-time hint for when tags exist, but unknown slugs deterministically go through dynamic render → notFound() → 404. Verified all 18 static reader routes 200, all 7 dynamic routes return 200 for valid IDs and 404 (not 500) for unknown ones, all 6 API smoke checks return their expected status codes (200 / 401 / 404).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
HIGH fixes: - register_from_browser: rollback private blob + cleanup temp dir on store.insert() failure (#4) - Orphan detection: roundtrip check prevents lossy unsanitize_path from causing false-positive deletions (#1, #10) - --from-token reads from stdin when value is "-" to avoid shell history exposure (#9) MEDIUM fixes: - swap switch(): rollback CC credentials on set_active_cli failure (#11) - register_account_from_profile: delete orphaned private blob on insert failure (#17) - remove_account: reorder to clear pointers + remove DB row before irreversible file deletions (#18) - desktop switch(): propagate DB update failures instead of ignoring (#22) - account.rs: propagate DB permission-setting failures (#20) - project.rs: post-move failures become warnings instead of errors when directory already moved (#16) - account remove --json: emit structured JSON response (#28) - doctor summary: count expired accounts as warnings (#26) LOW fixes: - doctor: surface store.list() failures via db_error field (#29) - keychain status: distinguish "no credential" from "access error" (#30)
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
Independent Codex audit (5-dim mini, threadId 019de662) flagged 2 High + 8 Medium + 1 Low across the templates feature surface. Independent verification (threadId 019de792) confirmed 10 fixes landed; #11 is deferred with rationale. ### High #1 prerun probe ignored route auth — `probe_sync` in `automations/prerun.rs` now attaches `Authorization: Bearer <key>` (or `Basic <key>` when `auth_scheme` is set) from the route's `GatewayConfig.api_key` when non-empty. LiteLLM / Cloudflare AI Gateway endpoints behind auth no longer false- fail with 401. #2 sidecar swap in `templates_apply_pending` — `commands_templates.rs` now rejects early when `pending.automation_id != automation_id`. Without this, an attacker (or a buggy caller) could pair automation A's pending- changes.json with automation B's broader apply policy. ### Medium #3 Ollama probe detection too broad — was `cfg.base_url.contains ("/api/")` which false-positives on `/api/v1` (OpenRouter, LiteLLM). Now narrowed to `:11434` port OR explicit `/api/tags` path. #4 hardcoded `~/.claudepot` — both `templates_pending_changes` and `templates_read_report` now use `claudepot_data_dir()` from claudepot-core, honoring `CLAUDEPOT_DATA_DIR` overrides correctly. #5 `supported_platforms` was only enforced in `templates_list` — `templates_install` now ALSO rejects when `!bp.supports(HostPlatform::current())`. The contract is symmetric: a direct IPC bypass of the gallery filter no longer lets a macOS-only template instantiate on Linux/Windows. #6 Windows ACL trusted `USERNAME` — `tighten_consent_acl` now resolves the current user SID via `whoami /user /fo csv /nh`, grants `*<SID>:F` in icacls (well-known SID notation), grants SYSTEM via the literal `S-1-5-18` SID, and surfaces every failure path through `tracing::warn` instead of swallowing silently. #7 cron test wrote `CLAUDE_OAUTH_TOKEN` to disk via `automation.extra_env` (which `install_shim` renders into `run.sh`). Token now lives in a 0600 sibling file (`<tmp>/.oauth-token`); a 0700 wrapper script (`<tmp>/claude-with-token.sh`) reads the file at fire time and exec's the real claude. The shim sees only the wrapper path, never the token. #8 cron test scheduled `now + 1 minute`, which races the case where `install_shim + scheduler.register` span past the next minute boundary — launchd would defer to the same time tomorrow and the test would hang. Now schedules `now + 2 minutes`; deadline extended to 180s. #9 cron test passed on a `"result":` substring match, which is too permissive — error rows mention `result` too. Now parses `stdout.log` as a JSON event array, finds the terminal `result` event, asserts `is_error == false`. Verified locally: the cron run produced `is_error=false result_first_80= CRON_TEMPLATE_OK`. #10 `CronCleanupGuard` did not restore the previous `CLAUDEPOT_DATA_DIR` — risk of polluting subsequent tests in the same process. Now captures the prior value as `Option<OsString>` at construction and restores it on Drop. Also explicitly zero-fills + unlinks the token file so a tempdir-removal race doesn't leave the token on disk. ### Low (deferred) #11 validator rebuilds globset matcher per call. Apply is interactive (~5-50 items × 1-3 globs each, user reviews + clicks per run); not a hot loop. Caching would require an `ApplyConfig`-level matcher cache and is not worth the complexity. Documented in the audit-fix verification report. ### Verification Independent Codex re-audit on a fresh thread: | Status | Count | |---|---:| | FIXED | 10 | | NOT FIXED | 0 | | PARTIAL | 0 | | REGRESSED | 0 | | DEFERRED | 1 | Local validation: - `cargo test --workspace` — all green - `cargo test -p claudepot-core --test templates_real_llm cron_schedule_fires -- --ignored` — passes with new is_error=false assertion (110s wall, ~$0.30) Audit threadId: 019de662-808c-7b62-80b3-e85d373f92b5 Verify threadId: 019de792-3515-7533-aac0-0203a96388be
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
fix(web): /c/[slug] 500 → 404 on unknown tag
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
Independent Codex audit (5-dim mini, threadId 019de662) flagged 2 High + 8 Medium + 1 Low across the templates feature surface. Independent verification (threadId 019de792) confirmed 10 fixes landed; #11 is deferred with rationale. ### High #1 prerun probe ignored route auth — `probe_sync` in `automations/prerun.rs` now attaches `Authorization: Bearer <key>` (or `Basic <key>` when `auth_scheme` is set) from the route's `GatewayConfig.api_key` when non-empty. LiteLLM / Cloudflare AI Gateway endpoints behind auth no longer false- fail with 401. #2 sidecar swap in `templates_apply_pending` — `commands_templates.rs` now rejects early when `pending.automation_id != automation_id`. Without this, an attacker (or a buggy caller) could pair automation A's pending- changes.json with automation B's broader apply policy. ### Medium #3 Ollama probe detection too broad — was `cfg.base_url.contains ("/api/")` which false-positives on `/api/v1` (OpenRouter, LiteLLM). Now narrowed to `:11434` port OR explicit `/api/tags` path. #4 hardcoded `~/.claudepot` — both `templates_pending_changes` and `templates_read_report` now use `claudepot_data_dir()` from claudepot-core, honoring `CLAUDEPOT_DATA_DIR` overrides correctly. #5 `supported_platforms` was only enforced in `templates_list` — `templates_install` now ALSO rejects when `!bp.supports(HostPlatform::current())`. The contract is symmetric: a direct IPC bypass of the gallery filter no longer lets a macOS-only template instantiate on Linux/Windows. #6 Windows ACL trusted `USERNAME` — `tighten_consent_acl` now resolves the current user SID via `whoami /user /fo csv /nh`, grants `*<SID>:F` in icacls (well-known SID notation), grants SYSTEM via the literal `S-1-5-18` SID, and surfaces every failure path through `tracing::warn` instead of swallowing silently. #7 cron test wrote `CLAUDE_OAUTH_TOKEN` to disk via `automation.extra_env` (which `install_shim` renders into `run.sh`). Token now lives in a 0600 sibling file (`<tmp>/.oauth-token`); a 0700 wrapper script (`<tmp>/claude-with-token.sh`) reads the file at fire time and exec's the real claude. The shim sees only the wrapper path, never the token. #8 cron test scheduled `now + 1 minute`, which races the case where `install_shim + scheduler.register` span past the next minute boundary — launchd would defer to the same time tomorrow and the test would hang. Now schedules `now + 2 minutes`; deadline extended to 180s. #9 cron test passed on a `"result":` substring match, which is too permissive — error rows mention `result` too. Now parses `stdout.log` as a JSON event array, finds the terminal `result` event, asserts `is_error == false`. Verified locally: the cron run produced `is_error=false result_first_80= CRON_TEMPLATE_OK`. #10 `CronCleanupGuard` did not restore the previous `CLAUDEPOT_DATA_DIR` — risk of polluting subsequent tests in the same process. Now captures the prior value as `Option<OsString>` at construction and restores it on Drop. Also explicitly zero-fills + unlinks the token file so a tempdir-removal race doesn't leave the token on disk. ### Low (deferred) #11 validator rebuilds globset matcher per call. Apply is interactive (~5-50 items × 1-3 globs each, user reviews + clicks per run); not a hot loop. Caching would require an `ApplyConfig`-level matcher cache and is not worth the complexity. Documented in the audit-fix verification report. ### Verification Independent Codex re-audit on a fresh thread: | Status | Count | |---|---:| | FIXED | 10 | | NOT FIXED | 0 | | PARTIAL | 0 | | REGRESSED | 0 | | DEFERRED | 1 | Local validation: - `cargo test --workspace` — all green - `cargo test -p claudepot-core --test templates_real_llm cron_schedule_fires -- --ignored` — passes with new is_error=false assertion (110s wall, ~$0.30) Audit threadId: 019de662-808c-7b62-80b3-e85d373f92b5 Verify threadId: 019de792-3515-7533-aac0-0203a96388be
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
fix(web): /c/[slug] 500 → 404 on unknown tag
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
HIGH fixes: - register_from_browser: rollback private blob + cleanup temp dir on store.insert() failure (#4) - Orphan detection: roundtrip check prevents lossy unsanitize_path from causing false-positive deletions (#1, #10) - --from-token reads from stdin when value is "-" to avoid shell history exposure (#9) MEDIUM fixes: - swap switch(): rollback CC credentials on set_active_cli failure (#11) - register_account_from_profile: delete orphaned private blob on insert failure (#17) - remove_account: reorder to clear pointers + remove DB row before irreversible file deletions (#18) - desktop switch(): propagate DB update failures instead of ignoring (#22) - account.rs: propagate DB permission-setting failures (#20) - project.rs: post-move failures become warnings instead of errors when directory already moved (#16) - account remove --json: emit structured JSON response (#28) - doctor summary: count expired accounts as warnings (#26) LOW fixes: - doctor: surface store.list() failures via db_error field (#29) - keychain status: distinguish "no credential" from "access error" (#30)
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
Independent Codex audit (5-dim mini, threadId 019de662) flagged 2 High + 8 Medium + 1 Low across the templates feature surface. Independent verification (threadId 019de792) confirmed 10 fixes landed; #11 is deferred with rationale. ### High #1 prerun probe ignored route auth — `probe_sync` in `automations/prerun.rs` now attaches `Authorization: Bearer <key>` (or `Basic <key>` when `auth_scheme` is set) from the route's `GatewayConfig.api_key` when non-empty. LiteLLM / Cloudflare AI Gateway endpoints behind auth no longer false- fail with 401. #2 sidecar swap in `templates_apply_pending` — `commands_templates.rs` now rejects early when `pending.automation_id != automation_id`. Without this, an attacker (or a buggy caller) could pair automation A's pending- changes.json with automation B's broader apply policy. ### Medium #3 Ollama probe detection too broad — was `cfg.base_url.contains ("/api/")` which false-positives on `/api/v1` (OpenRouter, LiteLLM). Now narrowed to `:11434` port OR explicit `/api/tags` path. #4 hardcoded `~/.claudepot` — both `templates_pending_changes` and `templates_read_report` now use `claudepot_data_dir()` from claudepot-core, honoring `CLAUDEPOT_DATA_DIR` overrides correctly. #5 `supported_platforms` was only enforced in `templates_list` — `templates_install` now ALSO rejects when `!bp.supports(HostPlatform::current())`. The contract is symmetric: a direct IPC bypass of the gallery filter no longer lets a macOS-only template instantiate on Linux/Windows. #6 Windows ACL trusted `USERNAME` — `tighten_consent_acl` now resolves the current user SID via `whoami /user /fo csv /nh`, grants `*<SID>:F` in icacls (well-known SID notation), grants SYSTEM via the literal `S-1-5-18` SID, and surfaces every failure path through `tracing::warn` instead of swallowing silently. #7 cron test wrote `CLAUDE_OAUTH_TOKEN` to disk via `automation.extra_env` (which `install_shim` renders into `run.sh`). Token now lives in a 0600 sibling file (`<tmp>/.oauth-token`); a 0700 wrapper script (`<tmp>/claude-with-token.sh`) reads the file at fire time and exec's the real claude. The shim sees only the wrapper path, never the token. #8 cron test scheduled `now + 1 minute`, which races the case where `install_shim + scheduler.register` span past the next minute boundary — launchd would defer to the same time tomorrow and the test would hang. Now schedules `now + 2 minutes`; deadline extended to 180s. #9 cron test passed on a `"result":` substring match, which is too permissive — error rows mention `result` too. Now parses `stdout.log` as a JSON event array, finds the terminal `result` event, asserts `is_error == false`. Verified locally: the cron run produced `is_error=false result_first_80= CRON_TEMPLATE_OK`. #10 `CronCleanupGuard` did not restore the previous `CLAUDEPOT_DATA_DIR` — risk of polluting subsequent tests in the same process. Now captures the prior value as `Option<OsString>` at construction and restores it on Drop. Also explicitly zero-fills + unlinks the token file so a tempdir-removal race doesn't leave the token on disk. ### Low (deferred) #11 validator rebuilds globset matcher per call. Apply is interactive (~5-50 items × 1-3 globs each, user reviews + clicks per run); not a hot loop. Caching would require an `ApplyConfig`-level matcher cache and is not worth the complexity. Documented in the audit-fix verification report. ### Verification Independent Codex re-audit on a fresh thread: | Status | Count | |---|---:| | FIXED | 10 | | NOT FIXED | 0 | | PARTIAL | 0 | | REGRESSED | 0 | | DEFERRED | 1 | Local validation: - `cargo test --workspace` — all green - `cargo test -p claudepot-core --test templates_real_llm cron_schedule_fires -- --ignored` — passes with new is_error=false assertion (110s wall, ~$0.30) Audit threadId: 019de662-808c-7b62-80b3-e85d373f92b5 Verify threadId: 019de792-3515-7533-aac0-0203a96388be
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
fix(web): /c/[slug] 500 → 404 on unknown tag
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Route audit found one dead-end:
/c/<anything>returned 500 (Pages Router/_errorshell) instead of 404 when the slug wasn't in the tags table.Other dynamic routes (
/post/[id],/u/[username],/projects/[slug],/office/persona/[name],/office/decision/[id]) all 404 correctly. The difference:/c/[slug]is the only one withgenerateStaticParams. With tags empty (fresh deploy, no submissions yet → no auto-tags), it returns[]and Next.js 15's static optimizer marked the route as prerendered-with-zero-params. Unknown slugs missed both the static index and the App Router's dynamic→notFound() path.export const dynamic = "force-dynamic"keepsgenerateStaticParamsas a build-time hint for when tags exist, but guarantees unknown slugs render dynamically →notFound()→ 404 every time.Coverage from the same audit
No other dead ends found.
Test plan
https://claudepot.com/c/anythingand confirm 404 with App Router not-found.tsx ("That page slipped through the gravity well") rather than the Pages Router 500 shell.