Skip to content

observability(server): say when a request loses its project config - #3457

Merged
kojiwakayama merged 2 commits into
mainfrom
fix/observe-lost-project-config
Aug 7, 2026
Merged

observability(server): say when a request loses its project config#3457
kojiwakayama merged 2 commits into
mainfrom
fix/observe-lost-project-config

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

What

resolveAdapter returns a ConfigResolutionOutcome naming which branch produced config, and the security fallback logs a warn carrying it. "No hosted config for this release; using defaults" goes from debug to warn.

This is instrumentation, not a fix. It exists to make an unsolved bug diagnosable.

The bug it instruments

codersociety.preview.veryfront.com serves the platform-default CSP instead of the project's on roughly a third of renders — same URL, same authenticated request. The two responses are byte-identical HTML; only the headers differ, because the project's two veryfront.config.ts versions differ only by a security.csp block. Nothing in production logs says so.

Reproducible in one loop:

plain #1: … data: https://cdn.codersociety.com
plain #2: … data: https://cdn.codersociety.com
plain #3: … data:                                ← project CSP gone
plain #4: … data:

with a clean bimodal split on runtime.resolve_adapter — 57–61 ms when the config is lost, 469–1091 ms when it is not, no overlap across 8 samples.

Why it was undiagnosable

resolveProjectRuntimeContext substitutes the process-wide SecurityConfigLoader for an absent project config. That is correct for the config-less control-plane endpoints it was written for, and silent everywhere else: the response is a 200 whose policy is the platform floor rather than the tenant's, which no downstream consumer can distinguish from a project that configured nothing.

Four unrelated branches reach it — an inherited caller config, a deliberate defer, a hosted 404, and a project that publishes no config — and the returned config: undefined cannot tell them apart. Every one of them logged at debug.

What this adds

  • ConfigResolutionOutcome: inherited | local | deferred | hosted | hosted-absent
  • A warn at the point of substitution carrying the outcome plus the request's source identity
  • deferred is excluded — a config-less security context is the intended shape for signed control-plane requests, not a degradation

Why not the fix itself

Six hypotheses for the underlying race have been falsified: content staleness (both sources now carry the CSP and it still flips), pod boot cache (fleet is on a 70-minute-old ReplicaSet, post-dating the config change), API inconsistency (12/12 consistent), studio_embed, release-pin, and CSS recompilation. Each was killed by measurement, not argument.

configOutcome discriminates between every remaining hypothesis in a single production sample. That is the cheapest path to the actual fix.

Verification

119 files / 1525 steps across src/server/runtime-handler and src/security, plus full unit suite green: 3811 passed, 0 failed. deno check, lint, fmt clean.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of missing hosted configuration by safely falling back to defaults.
    • Preserved error handling for hosted configuration failures that are not missing-resource responses.
    • Added clearer diagnostics for configuration fallback and inherited security settings.
    • Improved tracking of configuration resolution states, including deferred, inherited, local, and hosted configurations.
    • Reduced unnecessary security warnings for deferred control-plane requests.
    • Added contextual warnings when proxied requests rely on process-wide security settings.

codersociety's preview serves the platform-default CSP instead of the
project's on roughly a third of renders, for the same URL and the same
authenticated request. The two responses are byte-identical HTML; only the
headers differ, because the project's two veryfront.config.ts versions differ
only by a security.csp block. Nothing in production logs said so.

resolveProjectRuntimeContext substitutes the process-wide SecurityConfigLoader
for an absent project config. That is correct for the config-less
control-plane endpoints it was written for, and silent everywhere else: the
response is a 200 whose policy is the platform floor rather than the tenant's,
which no downstream consumer can distinguish from a project that configured
nothing. Four unrelated branches reach it -- an inherited caller config, a
deliberate defer, a hosted 404, and a project that publishes no config -- and
the returned `config: undefined` cannot tell them apart.

Name the branch. resolveAdapter now returns a ConfigResolutionOutcome
alongside the config, and the fallback logs a warn carrying it plus the source
identity of the request. Deferred control-plane requests are excluded: a
config-less security context is their intended shape.

Also raise "No hosted config for this release; using defaults" from debug to
warn. For a project that genuinely publishes no config this is routine, but it
is indistinguishable here from a config that momentarily 404s, and both
degrade the same way downstream. At debug it was invisible in production while
a preview served the wrong CSP for hours.

This is instrumentation, not the fix. The mechanism that makes the config
resolve intermittently is still unknown -- the timing split is clean
(resolve_adapter 57-61ms when the config is lost, 469-1091ms when it is not)
but three candidate causes have already been falsified. This is what makes the
next production sample conclusive.

Verified: 119 files / 1525 steps across src/server/runtime-handler and
src/security. deno check, lint and fmt clean on src/server/runtime-handler.
@kojiwakayama
kojiwakayama requested a review from kwakayama as a code owner August 7, 2026 12:05
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bce429f9-88cf-44ef-8f3c-6541db04a423

📥 Commits

Reviewing files that changed from the base of the PR and between 3e6c204 and 21d71d9.

📒 Files selected for processing (1)
  • src/server/runtime-handler/project-runtime-context.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/server/runtime-handler/project-runtime-context.test.ts

📝 Walkthrough

Walkthrough

The adapter factory now reports how configuration was resolved, including absent hosted configuration. Proxy runtime context uses this outcome to warn when project security configuration is missing while excluding deferred control-plane requests.

Changes

Configuration resolution and runtime diagnostics

Layer / File(s) Summary
Configuration provenance resolution
src/server/runtime-handler/adapter-factory.ts, src/server/runtime-handler/adapter-factory.test.ts
resolveAdapter now returns outcomes for inherited, local, deferred, hosted, and hosted-absent configuration. Hosted 404 responses use hosted-absent and warning diagnostics. Tests cover direct and wrapped 404 responses.
Runtime missing-configuration warnings
src/server/runtime-handler/project-runtime-context.ts, src/server/runtime-handler/project-runtime-context.test.ts
Proxy requests without project security configuration emit contextual warnings unless the outcome is deferred. Tests cover deferred control-plane requests and inherited process-wide security configuration.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProjectRuntimeContext
  participant resolveAdapter
  participant SERVER
  ProjectRuntimeContext->>resolveAdapter: resolve adapter and configOutcome
  resolveAdapter-->>ProjectRuntimeContext: return configuration outcome
  ProjectRuntimeContext->>SERVER: warn for missing non-deferred project configuration
Loading

Possibly related PRs

Suggested reviewers: kwakayama, copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding server observability when a request loses its project configuration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/observe-lost-project-config

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server/runtime-handler/project-runtime-context.test.ts`:
- Around line 1362-1365: Extend the test around the deferred adapter outcome
assertion to capture emitted log records, then assert that none contains
"serving platform-default security headers". Keep the existing
adapter.configOutcome "deferred" assertion unchanged and verify the warning is
excluded specifically for the config-less control-plane request.

In `@src/server/runtime-handler/project-runtime-context.ts`:
- Around line 342-350: Remove or redact the raw project and request metadata
from both warning calls: the warning in
src/server/runtime-handler/project-runtime-context.ts lines 342-350 and the
corresponding warning in src/server/runtime-handler/adapter-factory.ts lines
350-357. Eliminate project, release, branch, environment, and pathname fields,
or replace them with an approved correlation identifier while preserving the
warnings themselves.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17a24ca9-3707-40af-a815-2f5f17213299

📥 Commits

Reviewing files that changed from the base of the PR and between 375412a and 3e6c204.

📒 Files selected for processing (4)
  • src/server/runtime-handler/adapter-factory.test.ts
  • src/server/runtime-handler/adapter-factory.ts
  • src/server/runtime-handler/project-runtime-context.test.ts
  • src/server/runtime-handler/project-runtime-context.ts

Comment thread src/server/runtime-handler/project-runtime-context.test.ts
Comment on lines +342 to +350
logger.warn("No project config for this request; serving platform-default security headers", {
projectSlug: projectRes.projectSlug,
projectId: projectRes.projectId,
configOutcome: adapterRes.configOutcome,
releaseId: envRes.releaseId ?? null,
branch: reqCtx.branch ?? null,
environmentName: projectRes.environmentName ?? null,
resolvedEnvironment: envRes.resolvedEnvironment ?? null,
pathname: input.url.pathname,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove raw project and request metadata from both warnings.

Both warning paths log tenant identifiers and request-derived values. These values can contain customer data or private infrastructure details. Use redacted values or an approved correlation identifier.

  • src/server/runtime-handler/project-runtime-context.ts#L342-L350: remove or redact project, release, branch, environment, and pathname fields.
  • src/server/runtime-handler/adapter-factory.ts#L350-L357: remove or redact project, release, branch, environment, and pathname fields.
📍 Affects 2 files
  • src/server/runtime-handler/project-runtime-context.ts#L342-L350 (this comment)
  • src/server/runtime-handler/adapter-factory.ts#L350-L357
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/runtime-handler/project-runtime-context.ts` around lines 342 -
350, Remove or redact the raw project and request metadata from both warning
calls: the warning in src/server/runtime-handler/project-runtime-context.ts
lines 342-350 and the corresponding warning in
src/server/runtime-handler/adapter-factory.ts lines 350-357. Eliminate project,
release, branch, environment, and pathname fields, or replace them with an
approved correlation identifier while preserving the warnings themselves.

Source: Coding guidelines

The deferred case asserted the adapter outcome but not the warning exclusion,
which is the part that actually matters. Every signed control-plane request
takes that path, so a regression there would warn on all of them and bury the
signal the warning exists to carry.

Capture log records and assert nothing reports the security fallback.
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

Addressed the first finding in 21d71d9dc — the deferred case now captures log records and asserts no fallback warning is emitted. Good catch: that exclusion is the part that actually matters, since every signed control-plane request takes that path.

On the second finding (redact project/release/branch/environment/pathname from both warnings) — I'm not implementing this, and want to give the reasoning rather than silently skip it.

The identifiers are the payload, not incidental context. This warning exists to answer one question: which project just lost its config? A production preview is serving the platform-default CSP instead of the tenant's on roughly a third of renders, and six hypotheses for why have already been falsified. configOutcome alone tells you a config went missing somewhere in a multi-tenant fleet; without projectSlug it cannot be correlated to the failing request, which is the entire purpose of the line.

It matches what the surrounding code already does. environment-resolution.ts logs projectSlug, projectId, environmentName, host, proxyEnv on its no-active-release warning. adapter-factory.ts logs projectSlug, projectId, releaseId, proxyEnv on config-load failure. Redacting only these two call sites would be inconsistent without making the fleet any safer.

The fields aren't customer data. A project slug is a public DNS label — codersociety.preview.veryfront.com. Release and environment IDs are internal identifiers. pathname is the path of a publicly-served page. These are server logs going to internal Loki, not user-facing output, and none of it is PII or credential material.

Happy to revisit if there's a specific field you'd single out — pathname is the only one I'd consider arguable, since a path could carry a query-ish segment on some routes. But dropping the project identifier would make the instrumentation useless for the bug it was written to diagnose.

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit e403311 Aug 7, 2026
31 checks passed
@kojiwakayama
kojiwakayama deleted the fix/observe-lost-project-config branch August 7, 2026 18:45
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