Skip to content

TML-2984: surface config-load failures on the config URI; retain the last-good project on reload failure#974

Open
SevInf wants to merge 14 commits into
mainfrom
tml-2984-slice-06-config-failure
Open

TML-2984: surface config-load failures on the config URI; retain the last-good project on reload failure#974
SevInf wants to merge 14 commits into
mainfrom
tml-2984-slice-06-config-failure

Conversation

@SevInf

@SevInf SevInf commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Slice 06 of the lsp-interpreter-diagnostics project (TML-2984, follows #939/#948/#971/#972) — the final build slice: config failures become visible and non-destructive.

Changes

  • Config-failure diagnostics (language-server/src/server.ts): any throw from the project-load flow (loadConfig, createControlStack, input resolution — opaque runtime errors from executed TypeScript) surfaces as one diagnostic on the config-file URI at (0,0)–(0,1) (PRISMA_NEXT_CONFIG_LOAD_FAILED, message carries the error text). Published via push unconditionally — the config file belongs to the TypeScript language service, so pull never reaches this server for it (tsserver configFileDiag precedent). Exactly-once per failed load is structural (the diagnostic publishes from the single shared load promise, guarded by isCurrentLoad so superseded loads stay silent). Cleared on the next successful load and when the last document under the config closes.
  • Last-good retention: the loading entry now carries the previous ProjectState; a failed reload resolves with the retained project — schema documents keep serving their full diagnostics (parse, symbol-table, and interpreter findings through the retained context) with the config diagnostic alongside, instead of today's wipe-everything (rust-analyzer switch_workspaces precedent: only switch to a broken workspace if you have none at all). A failed first load keeps today's behavior exactly.

Why

Before this slice, breaking your config while editing meant every schema diagnostic silently vanished — the server dropped the project and cleared the markers, leaving no clue why. Now the editor tells you two things at once: the config is broken (marker on the config file itself, position (0,0) since no spans exist into executed TS) and your schema work continues against the last working configuration. One deliberate re-pin: the test that encoded destroy-on-reload was rewritten to encode retention — that behavior change is this slice's entire charter.

One residual, by design of the lazy load lifecycle: after a failed first load with no schema documents open, the config marker persists until a schema document reopens (no load fires for a config with no open inputs). Self-heals with any document open; exercised in the upcoming playground QA.

Project spec + plan live under projects/lsp-interpreter-diagnostics/ (deleted at project close-out).

Summary by CodeRabbit

  • Bug Fixes

    • Preserve the previously successful (“last-good”) diagnostics when a configuration reload fails, keeping affected schema documents managed until a successful recovery.
    • Emit PRISMA_NEXT_CONFIG_LOAD_FAILED configuration-load-failure diagnostics exactly once per failing config, with deduplication across concurrent loads and suppression for superseded failures.
    • Clear configuration-failure diagnostics after a successful reload, and when the last related managed document is closed.
  • Tests

    • Added/expanded end-to-end coverage for config reload failure/success behavior, diagnostic deduplication, and cleanup/suppression.

@SevInf
SevInf requested a review from a team as a code owner July 14, 2026 16:45
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The language server preserves the last successfully loaded project during failed config reloads, publishes config-load diagnostics, clears them after recovery or project removal, and tests concurrent failures, pull clients, cleanup, and superseded loads.

Changes

Config failure recovery

Layer / File(s) Summary
Project load state and failure handling
packages/1-framework/3-tooling/language-server/src/server.ts
Loading tracks lastGood project state, publishes diagnostics for active config-load failures, restores prior project state when possible, and clears failure markers after recovery or project removal.
Config failure behavior coverage
packages/1-framework/3-tooling/language-server/test/server.test.ts
Tests verify failure publication, deduplication, pull-client behavior, last-good retention, recovery, document cleanup, and superseded-load suppression.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConfigWatcher
  participant LanguageServer
  participant LSPClient
  ConfigWatcher->>LanguageServer: startProjectLoad
  LanguageServer->>LanguageServer: retain lastGood project state
  LanguageServer->>LSPClient: publish config-load-failure diagnostic
  LanguageServer->>LanguageServer: restore lastGood project
  LanguageServer->>LSPClient: publish retained project diagnostics
Loading

Possibly related PRs

  • prisma/prisma-next#887: Refactored the managed-project and diagnostics lifecycle used by config reload failure handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: surfacing config-load failures and preserving the last-good project on reload failure.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2984-slice-06-config-failure

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 157.93 KB (0%)
postgres / emit 131.71 KB (0%)
mongo / no-emit 98.71 KB (0%)
mongo / emit 89.43 KB (0%)
cf-worker / no-emit 184.61 KB (0%)
cf-worker / emit 155.81 KB (0%)

@pkg-pr-new

pkg-pr-new Bot commented Jul 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@974

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@974

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@974

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@974

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@974

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@974

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@974

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@974

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@974

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@974

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@974

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@974

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@974

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@974

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@974

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@974

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@974

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@974

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@974

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@974

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@974

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@974

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@974

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@974

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@974

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@974

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@974

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@974

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@974

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@974

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@974

@prisma-next/config-loader

npm i https://pkg.pr.new/@prisma-next/config-loader@974

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@974

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@974

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@974

prisma-next

npm i https://pkg.pr.new/prisma-next@974

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@974

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@974

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@974

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@974

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@974

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@974

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@974

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@974

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@974

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@974

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@974

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@974

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@974

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@974

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@974

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@974

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@974

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@974

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@974

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@974

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@974

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@974

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@974

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@974

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@974

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@974

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@974

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@974

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@974

commit: b06b18b

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/1-framework/3-tooling/language-server/src/server.ts (1)

328-345: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Prevent superseded load failures from clearing the current project entry. resolveProjectIfLoadable() unconditionally calls stopManagingProject(configPath) on any rejection, so a stale load failure can delete a newer managedProjects entry that already replaced it. Gate that cleanup on the rejected promise still being the current load, and add a test assertion that the replacement project survives.

🤖 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 `@packages/1-framework/3-tooling/language-server/src/server.ts` around lines
328 - 345, Update resolveProjectIfLoadable’s rejection cleanup so
stopManagingProject(configPath) runs only when the rejected promise still
matches the current managedProjects loading entry. Preserve the replacement
entry when a superseded load fails, and add a test assertion confirming the
newer project remains managed.
🧹 Nitpick comments (1)
packages/1-framework/3-tooling/language-server/src/server.ts (1)

86-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider exporting CONFIG_LOAD_FAILED_CODE.

The test file hardcodes the literal 'PRISMA_NEXT_CONFIG_LOAD_FAILED' instead of importing this constant, so the two can drift silently if this value ever changes.

🤖 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 `@packages/1-framework/3-tooling/language-server/src/server.ts` around lines 86
- 89, Export CONFIG_LOAD_FAILED_CODE from server.ts so tests and other consumers
can import the single source of truth instead of hardcoding the error code
literal. Keep its current value unchanged.
🤖 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 `@packages/1-framework/3-tooling/language-server/src/server.ts`:
- Around line 78-83: Clarify the `lastGood` documentation to state that a failed
first load does not publish or serve a project state and leaves documents
unmanaged, while still publishing the config-failure diagnostic. Replace the
ambiguous “publishes nothing” wording without changing the
`publishConfigFailure` behavior or load handling.
- Around line 228-269: The failure branch in startProjectLoad must not restore
lastGood when hasManagedDocuments(configPath) is false. After
publishConfigFailure, delete the managedProjects entry when no documents remain;
only restore lastGood and return it when managed documents still exist,
preventing stale project resurrection and repeated diagnostics.

---

Outside diff comments:
In `@packages/1-framework/3-tooling/language-server/src/server.ts`:
- Around line 328-345: Update resolveProjectIfLoadable’s rejection cleanup so
stopManagingProject(configPath) runs only when the rejected promise still
matches the current managedProjects loading entry. Preserve the replacement
entry when a superseded load fails, and add a test assertion confirming the
newer project remains managed.

---

Nitpick comments:
In `@packages/1-framework/3-tooling/language-server/src/server.ts`:
- Around line 86-89: Export CONFIG_LOAD_FAILED_CODE from server.ts so tests and
other consumers can import the single source of truth instead of hardcoding the
error code literal. Keep its current value unchanged.
🪄 Autofix (Beta)

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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 85eab191-8cf1-4d35-88f4-3195c162051e

📥 Commits

Reviewing files that changed from the base of the PR and between 7adfdb5 and 1bbb66b.

⛔ Files ignored due to path filters (4)
  • projects/lsp-interpreter-diagnostics/plans/plan.md is excluded by !projects/**
  • projects/lsp-interpreter-diagnostics/slices/06-config-failure/briefs/d1-r1.md is excluded by !projects/**
  • projects/lsp-interpreter-diagnostics/slices/06-config-failure/spec.md is excluded by !projects/**
  • projects/lsp-interpreter-diagnostics/trace.jsonl is excluded by !projects/**
📒 Files selected for processing (2)
  • packages/1-framework/3-tooling/language-server/src/server.ts
  • packages/1-framework/3-tooling/language-server/test/server.test.ts

Comment thread packages/1-framework/3-tooling/language-server/src/server.ts
Comment thread packages/1-framework/3-tooling/language-server/src/server.ts
@SevInf
SevInf force-pushed the tml-2984-slice-06-config-failure branch from 1bbb66b to f399fd6 Compare July 15, 2026 13:23
Comment thread packages/1-framework/3-tooling/language-server/src/server.ts Outdated
Comment thread packages/1-framework/3-tooling/language-server/src/server.ts
const hadLoadedProject =
existing?.status === 'loaded' ||
(existing?.status === 'loading' && existing.hadLoadedProject);
const lastGood =

@SevInf SevInf Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Holy mother of clean code, 3 levels of nested ternaries. Are you proud of yourself?

Looks like you need good old helper function for both lastGood and configFiled

SevInf added 9 commits July 15, 2026 15:53
…etention)

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
A failed project load publishes one diagnostic on the config-file URI
at document start (push channel unconditionally -- the config file
belongs to the TypeScript language service, so pull never reaches us),
cleared on the next successful load or when the project is dropped for
document-lifecycle reasons. The loading entry now carries the previous
ProjectState: a broken reload restores it so open schema documents keep
their parse, symbol-table, and interpreter diagnostics, while a failed
first load keeps todays no-project behavior. Superseded loads stay
silent.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Review response: the parallel URI map dissolves into entry state -- a
failed first load records a `failed` entry (absence-like for project
resolution, memory of the published marker), the retention case carries
configFailed on the loaded entry, and the in-flight entry threads the
bit so chained loads clear correctly. A failure that lands after the
last document closed deletes the entry outright: no resurrection, no
zombie marker. The failed-load funnels now unmanage documents only when
the failure is the config's current state, so a stale rejection cannot
touch a newer entry. CONFIG_LOAD_FAILED_CODE is exported for tests.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…arker)

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…and-forget

Review-settled simplification: LSP clients keep per-server diagnostic
state, so empty publishes are harmless and the configFailed bookkeeping
bought nothing the failed status does not already provide. Every
current-load success and every settled-entry drop now publishes an
unconditional clear on the config URI. Publish-counting pins re-pin to
non-empty publish counts; retention pins wait for the non-empty marker
specifically.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…et clears)

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Review response: the last-good derivation reads as a plain function over
the three entry states, reused by the hadProject check in
stopManagingProject.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
@SevInf
SevInf force-pushed the tml-2984-slice-06-config-failure branch from c1987f9 to 843eca9 Compare July 15, 2026 16:00
} catch {
stopManagingProject(configPath);
// Unmanage documents only when the failure is the config's current
// state — a stale load's rejection must not touch a newer entry.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Stale load rejection can not touch a newer entry because all loads get the queued

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for writing entire novel. If what you say is true then the queue is incorrectly implemented. Rejection of load N must not be handled after either success or failure of load N+1

SevInf added 5 commits July 15, 2026 17:03
…trim narration

Review response: the currency gate stays -- load queuing orders
execution, but a superseded load rejection is delivered to awaiters
that grabbed the old promise before the synchronous entry replacement,
so an ungated funnel would delete the newer entry mid-flight. The gate
comment now carries that interleaving. Narrating and usage-enumerating
comment fragments trimmed.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Per-review comment pass: variant enumeration, next-line narration, and
condition restatements removed from server.ts; test comments trimmed to
the non-obvious rationale by the parallel pass. The lastGood member doc
keeps its review-requested sentence.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ome pure readers

Review-settled restructure: the load chain failure handler -- already
ordered strictly before any successor load by the queue -- now owns
first-load-failure unmanagement alongside the marker publish, so the
awaiter catches carry no side effects and no currency guards.
stopManagingProject folds into an unmanageDocuments helper; the
per-read association drops on failure disappear with it. The
superseded-failure survivor scenario is now deterministic: the newer
load result serves directly, pinned at exactly two resolutions.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
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