Skip to content

Replace api.updateSnapshot - #64204

Merged
Andrew Branch (andrewbranch) merged 30 commits into
microsoft:mainfrom
andrewbranch:api/snapshot-state
Sep 17, 2026
Merged

Andrew Branch (andrewbranch) merged 30 commits into
microsoft:mainfrom
andrewbranch:api/snapshot-state

Conversation

@andrewbranch

@andrewbranch Andrew Branch (andrewbranch) commented Sep 8, 2026

Copy link
Copy Markdown
Member

Closes #64154. Read that issue for the big picture overview—here’s the list of changes and decisions:

  • Removes tracking of a "latest snapshot" from the API.
  • api.updateSnapshot() is replaced by:
    • api.getCurrentLanguageServerSnapshot(changes?) available in LSP mode only
    • api.createSnapshot(changes?) available always
  • You can create a new snapshot based on any other with const newSnapshot = snapshot.update(changes)
  • changes takes three new operations:
    • snap.update({ createPrograms: [/* ... */] }) adds programs to any snapshot
    • snap.update({ reconfigurePrograms: [/* ... */] }) changes the root files, options, or references of a program created with createPrograms
    • snap.update({ ensurePrograms: [/* ... */] }) returns a snapshot where projects with the given IDs have up-to-date programs. snap.update({ ensurePrograms: true }) ensures all projects are up to date.
      • You can no longer pass oldProgram as an option in creating a program. Instead, a program can be incrementally updated by notifying the API of its changed files and using ensurePrograms:
        const s0 = api.createSnapshot({ createPrograms: [/* ... */] });
        const p0 = s0.operation.createdPrograms[0];
        // change files on disk or in VFS, then notify:
        const s1 = s0.update({
          fileChanges: { changed: [/* ... */] },
          ensurePrograms: [p0.id],
        });
      • Previously, in LSP mode, all projects were updated automatically as part of any updateSnapshot request. Now, you have to use ensurePrograms. (The exception is that projects returned by openProjects or openFiles are automatically updated without need for a separate ensurePrograms, even if they're already open/created. ensurePrograms is mainly needed in combination with fileChanges, so you can say which projects you care about.)
  • As seen above, a snapshot carries an operation with information about the request that created it. Currently only createPrograms and openFiles contribute to operation, since those result in the creation of projects with an ID that might not be known to the caller ahead of time.
  • project.id now has the type ProjectId = ConfiguredProjectId | InferredProjectId | SyntheticProjectId, each of which is a branded string. ConfiguredProjectId is a subtype of Path. The same ID is also exposed on program.id for convenience.
  • api.createProgram(rootFiles, options) is basically shorthand for api.createSnapshot({ createPrograms: [{ rootFiles, options }] }).operation.createdPrograms[0].
  • Renamed fileChanges to fileNotifications and APIFileChanges to FileNotifications

Copilot AI balanced review requested due to automatic review settings September 8, 2026 21:47
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Sep 8, 2026
@typescript-automation typescript-automation Bot added Author: Team For Milestone Bug PRs that fix a bug with a specific milestone labels Sep 8, 2026

Copilot AI 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.

🟡 Changes recommended

Snapshot refresh and repeated-open handling contain correctness issues, and removePrograms exposes an overly broad project-ID type.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Replaces latest-snapshot state with explicit snapshot creation and derivation, adding synthetic-program lifecycle support and branded project IDs.

Changes:

  • Adds createSnapshot, Snapshot.update, and LSP snapshot retrieval.
  • Supports creating, removing, and ensuring synthetic programs.
  • Updates protocols, generators, caches, and tests for the new model.
File summaries
File Description
tsc/internal/project/snapshothost.go Adds independent root snapshots.
tsc/internal/project/snapshot.go Adds synthetic-program operations.
tsc/internal/project/snapshot_test.go Tests synthetic lifecycle.
tsc/internal/project/session.go Passes clients explicitly during cloning.
tsc/internal/project/refcountcache_test.go Updates cache tests for synthetic programs.
tsc/internal/project/projectcollectionbuilder.go Manages synthetic projects and program updates.
tsc/internal/project/projectcollection.go Integrates synthetic projects into lookups.
tsc/internal/project/project.go Defines synthetic projects and IDs.
tsc/internal/project/project_stringer_generated.go Adds generated synthetic-kind text.
tsc/internal/api/session.go Implements the redesigned snapshot API.
tsc/internal/api/session_temporary_test.go Tests explicit-base updates.
tsc/internal/api/session_createprogram_test.go Tests snapshot-created programs.
tsc/internal/api/session_completion_test.go Migrates completion setup.
tsc/internal/api/session_apistate_test.go Tests LSP snapshot state and ownership.
tsc/internal/api/proto.go Defines new protocol methods and types.
tsc/internal/api/proto_test.go Tests ensurePrograms decoding.
tools/gen-proto/main.go Generates branded IDs and embedded interfaces.
tools/gen-proto/main_test.go Verifies generated protocol output.
packages/typescript/test/sync/astnav.test.ts Migrates synchronous AST navigation tests.
packages/typescript/test/sync/ast.test.ts Migrates synchronous AST tests.
packages/typescript/test/sync/api.bench.ts Migrates synchronous benchmarks.
packages/typescript/test/sync/api-generators.test.ts Updates generator parity coverage.
packages/typescript/test/diagnosticFormatter.test.ts Migrates diagnostic tests.
packages/typescript/test/async/astnav.test.ts Migrates asynchronous AST navigation tests.
packages/typescript/test/async/api.bench.ts Migrates asynchronous benchmarks.
packages/typescript/src/api/sync/api.ts Exposes the synchronous snapshot model.
packages/typescript/src/api/sourceFileCache.ts Supports branded project IDs in caching.
packages/typescript/src/api/proto.ts Adds snapshot request compatibility conversion.
packages/typescript/src/api/proto.generated.ts Updates generated wire declarations.
packages/typescript/src/api/async/api.ts Exposes the asynchronous snapshot model.
Review details

Files not reviewed (1)

  • tsc/internal/project/project_stringer_generated.go: Generated file
  • Files reviewed: 29/32 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread tsc/internal/project/snapshot.go Outdated
Comment thread tsc/internal/api/proto.go Outdated
Comment thread tsc/internal/api/session.go Outdated
@andrewbranch

Copy link
Copy Markdown
Member Author

I have a refactor on top of this to use strongly typed project IDs that are not just tspath.Path, but it was a big diff so I didn't include it in this branch. It's a very nice cleanup though.

Copilot AI 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.

🟡 Changes recommended

LSP reconciliation can mishandle close-and-reopen requests, and solution-wide operations may consume stale synthetic programs.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • tsc/internal/project/project_stringer_generated.go: Generated file

Suppressed comments (1)

tsc/internal/api/session.go:1245

  • The same pre-request filtering breaks close-and-reopen for files: when a currently owned file appears in both lists, OpenFiles is removed here and CloseFiles is retained below, leaving the file closed. Independent snapshot reconciliation processes closes before opens, so the two APIs now produce different final states for the same change set. Compute both deltas from a temporary open-file state so the reopen wins.
	for uri := range apiRequest.OpenFiles.Keys() {
		path := s.toPath(uri.FileName())
		if s.openFiles.Has(path) {
			apiRequest.OpenFiles.Delete(uri)
		} else {
  • Files reviewed: 31/34 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread tsc/internal/api/session.go Outdated
Comment thread tsc/internal/project/projectcollection.go

Copilot AI 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.

🟡 Changes recommended

Malformed or unknown program identifiers can currently cause incorrect success, unintended removal, or a server panic.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • tsc/internal/project/project_stringer_generated.go: Generated file

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

tsc/internal/project/projectcollectionbuilder.go:310

  • Unknown project IDs are silently ignored because DidRequestProject returns without indicating whether it found anything. Consequently, ensurePrograms can report success even though the requested program is absent (for example, when an ID from an unrelated snapshot is supplied). Validate every requested ID against this snapshot and return a client error when one is missing.
  • Files reviewed: 33/36 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread tsc/internal/api/session.go
Comment thread tsc/internal/project/project.go

Copilot AI 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.

🔵 Needs a closer look

An API-owned configured project cannot be reopened after its tsconfig is deleted and recreated.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

tsc/internal/project/projectcollectionbuilder.go:212

  • An already-owned openProjects request is reduced to EnsurePrograms, which only updates an existing map entry. If the tsconfig was deleted, ensuring it removes the configured project but leaves the API open reference; after the file is recreated, another openProjects call can therefore never reach findOrCreateProject, so the project remains absent. Preserve a distinct “ensure open project” signal that can recreate a missing entry without incrementing the API ref count.
  • Files reviewed: 38/40 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@weswigham Wesley Wigham (weswigham) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it looks good, but if you'd like, I think we could use more dedicated test coverage of forking snapshots. I only saw, like, one assertion that file contents didn't leak between siblings reading through the test changes - but there's a lot more potential shared state between sibling snapshots than just file contents. There's obviously nothing in the typesystem ensuring that we're only sharing data by-copy on snapshot update and not by-ref, after all, and by-ref was sufficient for sequential, single snapshots. In particular, I don't see any changes to Snapshot.Clone, which implies it was already totally multi-snapshot-safe, which is great if true, but we're not really testing it rigorously. Auto-imports caches, config file caches, and probably more all look probably immutable/independent, but we're not really exercising them via tests.

@github-project-automation github-project-automation Bot moved this from Not started to Needs merge in PR Backlog Sep 17, 2026
@andrewbranch

Andrew Branch (andrewbranch) commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

That's never a bad thing to have more of, and while there could always be some corner where there is a bug, I think we do have a decent amount of pre-existing coverage of that because we actually have had the possibility of sibling snapshots for a long time, just not ones that an API consumer could get their hands on, and not siblings that were themselves cloned. Both auto-imports snapshots and the previous dedicated “temporary file update” snapshots were forking the main line, with auto-imports having the possibility of being adopted back into the main line if it never actually had a sibling. But those, along with routine parallel processing of LSP requests, means we have spent a while hardening the snapshot's immutability guarantee by tracking down panics from editor telemetry.

@andrewbranch
Andrew Branch (andrewbranch) added this pull request to the merge queue Sep 17, 2026
Merged via the queue into microsoft:main with commit 898322c Sep 17, 2026
26 checks passed
@andrewbranch
Andrew Branch (andrewbranch) deleted the api/snapshot-state branch September 17, 2026 21:53
@github-project-automation github-project-automation Bot moved this from Needs merge to Done in PR Backlog Sep 17, 2026
John Reilly (johnnyreilly) added a commit to TypeStrong/ts-loader that referenced this pull request Sep 19, 2026
@johnnyreilly

Copy link
Copy Markdown

Hey Andrew Branch (@andrewbranch)!

I migrated ts-loader's branch for the new API to these changes in this commit: TypeStrong/ts-loader@339d06d

It went pretty well - though it looks like there might be a regression. I enclose a report written with the help of Claude. Hopefully it makes sense.

Changes made

All changes are in src/typeScriptApi.ts, plus one test helper that also
called the TypeScript API directly.

  1. api.updateSnapshot(...)createSnapshot() / snapshot.update()
    The API no longer tracks a "latest snapshot" itself, so ts-loader's own
    updateSnapshot() helper now does it explicitly:

    • No previous snapshot yet (first compile) → api.createSnapshot(params).
    • Otherwise → previousSnapshot.update(params), layering the change over
      the snapshot ts-loader already holds.
  2. fileChangesfileNotifications
    Renamed field (and the underlying APIFileChangesFileNotifications
    type) carried straight through — same shape (invalidateAll /
    changed / deleted), just a new key/name.

  3. snapshot.getProject(configFileName)snapshot.getConfiguredProject(configFileName)
    getProject now takes a branded ProjectId, not a config file path.
    Since every call site in ts-loader was looking up a project by its
    tsconfig path (the real config, or one of ts-loader's own synthetic
    per-orphan-file configs), all three were swapped for the new
    path-keyed getConfiguredProject.

  4. api.createProgram(rootFiles, options)api.createProgram(rootFiles, compilerOptions, options)
    compilerOptions moved out of the options object into its own
    positional argument; configFileParsingDiagnostics stays in the
    (now smaller) options object.

  5. test/comparison-tests/getProgram.js
    A test helper that called api.updateSnapshot(...) /
    snapshot.getProject(...) directly — updated the same way as 1 and 3 above were.

Verification

  • yarn build — clean.
  • yarn lint (typecheck + oxlint) — clean.
  • yarn comparison-tests49 / 50 suites pass.

The one remaining failure: sourceMapsShouldConsiderInputSourceMap

An upstream Go panic in the TypeScript nightly, no project found for opened file .../App.vue.ts, thrown from handleUpdateSnapshot
createSnapshotResponsecreateSnapshotOperationResponse. It
reproduces specifically when a file that isn't yet part of any project
is opened via snapshot.update() (the wire method behind
Snapshot.update), on a snapshot that isn't the first one created.

Confirmed by temporarily forcing every call through a fresh
api.createSnapshot() instead of ever calling .update() — the panic
disappeared entirely, isolating it to .update()'s handling of newly
opened files.

Report on panic

Snapshot.update() panics opening a file not yet in any project, on a non-first snapshot (#64204)
Migrating ts-loader to the new snapshot API (typescript@next 7.1.0-dev.20260919.1, i.e. #64204 as merged), we hit a server-side Go panic that's specific to Snapshot.update() and doesn't reproduce via api.createSnapshot().
Repro shape:

const s0 = api.createSnapshot({
  openProjects: [tsconfigPath],
  openFiles: [entryFile], // e.g. index.ts
});
// ... later, after entryFile's own compile has run and s0 is the "current" snapshot ...
const s1 = s0.update({
  openFiles: [neverBeforeSeenFile], // not part of any project yet (real, inferred, or synthetic)
});
// -> throws: "Module build failed ... panic: no project found for opened file <neverBeforeSeenFile>"

neverBeforeSeenFile here is a purely virtual identity (a Vue SFC's extracted <script lang="ts"> block, served via a custom readFile/fileExists host override, never written to real disk) that isn't included by any tsconfig yet. Per SnapshotRequestChangesParams.openFiles's own doc comment, this should fall back to loading it into the inferred project — and that's exactly what happens when the same openFiles request is made via api.createSnapshot() instead of s0.update(). Swapping every .update() call in our integration for a fresh createSnapshot() (losing the cache reuse that .update() exists to provide) made the panic disappear entirely across our test suite, which is what isolates this to .update()'s (handleUpdateSnapshot's) specific handling of newly-opened, not-yet-tracked files.
Full panic:

Error: panic: no project found for opened file /.../App.vue.ts
goroutine 1 [running]:
runtime/debug.Stack()
.../runtime/debug/stack.go:26
github.com/microsoft/TypeScript/tsc/internal/ipc.(*SyncConn).handleRequest.func1()
.../tsc/internal/ipc/conn_sync.go:122
panic(...)
.../runtime/panic.go:859
github.com/microsoft/TypeScript/tsc/internal/api.(*Session).createSnapshotOperationResponse(...)
.../tsc/internal/api/session.go:4395
github.com/microsoft/TypeScript/tsc/internal/api.(*Session).createSnapshotResponse(...)
.../tsc/internal/api/session.go:4333
github.com/microsoft/TypeScript/tsc/internal/api.(*Session).handleUpdateSnapshot(...)
.../tsc/internal/api/session.go:1227

@andrewbranch

Copy link
Copy Markdown
Member Author

a Vue SFC's extracted <script lang="ts"> block, served via a custom readFile/fileExists host override, never written to real disk

John Reilly (@johnnyreilly), according to Copilot, this is happening because ts-loader is using the filename App.vue.ts for openFiles, while the host's readFile/fileExists only knows about App.vue. I'm opening a PR to turn this panic into an API error, but can you double check your implementation and see if that explains it?

@johnnyreilly

Copy link
Copy Markdown

I think the answer is partly but not entirely. With a little bit of help from Claude I've made some changes which you can see in TypeStrong/ts-loader@693fd7c - here's a write up which is hopefully readable enough: (incidentally I'm not sure all of the changes in my last commit will be required when your changes land?)


Issue 1 (the one you diagnosed) — confirmed, and it was a bug on our side. As you guessed, we were aliasing App.vue to App.vue.ts for every API call (including openFiles), but our fs.readFile/fileExists host overrides only knew the file under its real name. Fixed on our end by having those overrides fall back to the pre-alias name.

Issue 2 — a second, distinct repro of the same panic, Windows-only, and it corrects something in my earlier report. Once issue 1 was fixed, we still hit "no project found for opened file" on Windows for a different scenario: our older appendTsSuffixTo option aliases a webpack entry file (e.g. index.vueindex.vue.ts) from the very start, so that virtual name is the first file we ever compile for that instance. That means the very first createSnapshot call has to open the project and this not-yet-a-member file together, in one request — and that combination panics too.

This is the part that corrects my original report: I'd claimed the panic was specific to Snapshot.update() and didn't reproduce via createSnapshot(). To test that, we tried splitting our call into a project-only createSnapshot() followed by a separate .update() that opens the file — and the identical panic just moved from handleCreateSnapshot to handleUpdateSnapshot. So the real trigger isn't create vs. update at all — it's opening a file together with a project it doesn't belong to, full stop, regardless of which call does it.

We worked around it on our end by catching that failure and retrying without openFiles, letting our existing inferred/synthetic-project fallback take over — so this isn't blocking us. But given it reproduces via createSnapshot() too, it seemed worth flagging in case it changes the shape of the fix, or is useful as a second test case alongside the original one.

@andrewbranch

Copy link
Copy Markdown
Member Author

Hm, I've investigated, but I can't reproduce that. Can you get Claude to generate a contained repro, or even repro instructions tied to a specific commit of your PR?

@johnnyreilly

John Reilly (johnnyreilly) commented Sep 22, 2026

Copy link
Copy Markdown

Yup will do - I wonder if you haven't been able to repro because it only surfaced on Windows during my testing? Claude has reproduced - I'll get it to drop a comment below.

See failure on Windows: https://github.com/TypeStrong/ts-loader/actions/runs/35692331446/job/106631768957

@johnnyreilly

Copy link
Copy Markdown

Got it — put together a minimal, ts-loader/webpack-free repro. It only reproduces on Windows, which is presumably why you couldn't hit it locally.

Repro instructions (tied to a specific commit):

  1. Check out TypeStrong/ts-loader at commit be6fe5a5 (branch copilot/implement-new-tsgo-api-support)
  2. cd repro-no-project-found-for-opened-file && node repro.js (needs typescript@next — we're on 7.1.0-dev.20260919.1)

Or standalone, no checkout needed — just typescript@next installed:

// repro.js
const path = require('path');
const { API } = require('typescript/unstable/sync');

const projectDir = __dirname; // needs a tsconfig.json here, e.g. { "compilerOptions": { "module": "commonjs" } }
const tsconfigPath = path.join(projectDir, 'tsconfig.json');

// Never written to real disk - served only via the fs overrides below, and
// not listed by the tsconfig, so it isn't part of that project's file list.
const virtualFileName = path.join(projectDir, 'virtual.ts');
const virtualFiles = new Map([[virtualFileName, 'export const hello = "world";\n']]);

const api = new API({
  fs: {
    fileExists: fileName => (virtualFiles.has(fileName) ? true : undefined),
    readFile: fileName => virtualFiles.get(fileName),
  },
});

// The very first createSnapshot() call for this API instance, opening a
// project and a file not in that project in the same request.
const snapshot = api.createSnapshot({
  openProjects: [tsconfigPath],
  openFiles: [virtualFileName],
});
console.log('No panic:', !!snapshot);

On Windows this throws:

panic: no project found for opened file C:/.../virtual.ts
...
github.com/microsoft/TypeScript/tsc/internal/api.(*Session).handleCreateSnapshot(...)

On macOS it prints No panic: true and returns a normal snapshot.

Confirmed via GitHub Actions (windows-latest) vs. local macOS — same repro script, same typescript@next, only the OS differs. Happy to add more diagnostics to that script if it'd help narrow it down further (e.g. dumping the project's resolved file list before the panic).

@andrewbranch

Copy link
Copy Markdown
Member Author

John Reilly (@johnnyreilly) hmmm, I think I may have inadvertently fixed you with #64391. I think the issue was your drive letter getting accidentally lowercased by us during openFiles, which throws off your case-sensitive fileExists and readFile map lookups.

@johnnyreilly

Copy link
Copy Markdown

Oh nice! I'll try and test with the latest nightly today and report back

@johnnyreilly

John Reilly (johnnyreilly) commented Sep 23, 2026

Copy link
Copy Markdown

Your diagnosis is correct - I see C going in and c coming out. Maybe we should be handling it better on our side

John Reilly (johnnyreilly) added a commit to TypeStrong/ts-loader that referenced this pull request Sep 23, 2026
…jects

Per microsoft/TypeScript#64204 (comment)
and microsoft/TypeScript#64204 (comment),
the "no project found for opened file" failure traces back to a Windows
drive-letter casing mismatch. ts-loader was itself contributing to that:
`openProjects` always carried `configFilePath`'s resolvedFilePathCache'd
(lowercased) form, but `openFiles` and `fileNotifications.changed`/`deleted`
used `fileName`'s natural, unmodified casing - so a single snapshot request
could mix a lowercase-drive-letter project path with a differently-cased
file path.

Canonicalizes every file name sent to the API the same way project paths
already are, at the single point (`updateSnapshot`) where the wire params
are built - doesn't touch how `apiFileName` is used anywhere else (getSourceFile,
getConfiguredProject, dependency tracking, etc.), keeping this a narrow,
wire-boundary-only change.

The existing catch-and-retry workaround for this same failure shape stays
in place as a safety net regardless of how much this narrows the problem.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
John Reilly (johnnyreilly) added a commit to TypeStrong/ts-loader that referenced this pull request Sep 23, 2026
…jects

Per microsoft/TypeScript#64204 (comment)
and microsoft/TypeScript#64204 (comment),
the "no project found for opened file" failure traces back to a Windows
drive-letter casing mismatch. ts-loader was itself contributing to that:
`openProjects` always carried `configFilePath`'s resolvedFilePathCache'd
(lowercased) form, but `openFiles` and `fileNotifications.changed`/`deleted`
used `fileName`'s natural, unmodified casing - so a single snapshot request
could mix a lowercase-drive-letter project path with a differently-cased
file path.

Canonicalizes every file name sent to the API the same way project paths
already are, at the single point (`updateSnapshot`) where the wire params
are built - doesn't touch how `apiFileName` is used anywhere else
(getSourceFile, getConfiguredProject, dependency tracking, etc.), keeping
this a narrow, wire-boundary-only change.

This supersedes (and lets us remove) the earlier catch-and-retry workaround
for the same symptom: confirmed via .github/workflows/windows-test-probe.yml
that this fix alone (no retry logic) is sufficient - appendSuffixTo and the
full 50-suite comparison run both pass on Windows without it. Removed rather
than kept as a redundant safety net since it was catching *any* thrown error
and retrying, which risked masking a genuinely different failure (e.g. a
broken tsconfig) behind a confusing second error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@johnnyreilly

John Reilly (johnnyreilly) commented Sep 23, 2026

Copy link
Copy Markdown

Yeah I've moved ts-loader to using the normalised filenames for previousSnapshot.update and createSnapshot and that seems to resolve the problem and remove the need for our workaround. See details in this commit:

TypeStrong/ts-loader@788f2ea

Thanks Andrew Branch (@andrewbranch)!

John Reilly (johnnyreilly) added a commit to TypeStrong/ts-loader that referenced this pull request Sep 23, 2026
…jects

Upgrades to typescript@next 7.1.0-dev.20260922.1 and fixes the "no project
found for opened file" failure that persisted against it on Windows (see
microsoft/TypeScript#64204 (comment)
onwards).

Per microsoft/TypeScript#64204 (comment)
and microsoft/TypeScript#64204 (comment),
the failure traces back to a Windows drive-letter casing mismatch.
ts-loader was itself contributing to that: `openProjects` always carried
`configFilePath`'s resolvedFilePathCache'd (lowercased) form, but
`openFiles` and `fileNotifications.changed`/`deleted` used `fileName`'s
natural, unmodified casing - so a single snapshot request could mix a
lowercase-drive-letter project path with a differently-cased file path.

Canonicalizes every file name sent to the API the same way project paths
already are, at the single point (`updateSnapshot`) where the wire params
are built - doesn't touch how `apiFileName` is used anywhere else
(getSourceFile, getConfiguredProject, dependency tracking, etc.), keeping
this a narrow, wire-boundary-only change.

Confirmed via .github/workflows/windows-test-probe.yml that this fix alone
is sufficient - appendSuffixTo and the full 50-suite comparison run both
pass on Windows without any additional workaround.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author: Team For Milestone Bug PRs that fix a bug with a specific milestone

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[API] Redesign client-side snapshot state model

4 participants