Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sweet-vans-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/nextjs": patch
---

Fix keyless drift detection failure caused by invalid fs module methods.
6 changes: 3 additions & 3 deletions packages/nextjs/src/server/keyless-telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,18 @@ function getTelemetryFlagFilePath(): string {
async function tryMarkTelemetryEventAsFired(): Promise<boolean> {
try {
if (canUseKeyless) {
const { mkdir, writeFile } = nodeFsOrThrow();
const { mkdirSync, writeFileSync } = nodeFsOrThrow();
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify nodeFsOrThrow exposes sync variants in all runtimes.

We now rely on mkdirSync and writeFileSync. Ensure nodeFsOrThrow() always returns these in supported Next.js server runtimes (Node.js only), and its type definition reflects that.

Run this repo-scoped script to confirm the implementation and typings of nodeFsOrThrow:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate nodeFsOrThrow definition and ensure it returns mkdirSync/writeFileSync:"
rg -nP -C3 'export\s+(function|const)\s+nodeFsOrThrow\b|nodeFsOrThrow\s*\(' --type ts

echo
echo "Check for sync fs members in the returned object:"
rg -nP -C2 'mkdirSync|writeFileSync' --type ts | sed -n '1,200p'

Length of output: 10556


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate utils.ts in packages/nextjs/src/server/fs:"
fd --base-directory packages/nextjs/src/server/fs -t f utils.ts

echo
echo "Display first 200 lines of utils.ts to inspect nodeFsOrThrow definition and its typing:"
sed -n '1,200p' packages/nextjs/src/server/fs/utils.ts

Length of output: 1302


Enforce FS sync methods in types

At runtime nodeFsOrThrow() returns the real Node.js fs module (which includes mkdirSync, writeFileSync, etc.), so your usages in keyless-telemetry.ts are safe. However, because we import #safe-node-apis with @ts-ignore, nodeFsOrThrow() is currently typed as any and does not guarantee those sync methods at compile time.

To fully verify and enforce that callers always have access to the sync variants, please:

  • In packages/nextjs/src/server/fs/utils.ts, change the signature of nodeFsOrThrow to explicitly return Node’s fs type:
    // before
    const nodeFsOrThrow = () => {
      if (!nodeRuntime.fs) throwMissingFsModule('fs')
      return nodeRuntime.fs
    }
    // after
    const nodeFsOrThrow = (): typeof import('fs') => {
      if (!nodeRuntime.fs) throwMissingFsModule('fs')
      return nodeRuntime.fs
    }
  • Add a TypeScript declaration for #safe-node-apis so that its fs export is correctly typed. For example, create a packages/nextjs/src/server/fs/utils.d.ts (or in your global types) with:
    declare module '#safe-node-apis' {
      export const fs: typeof import('fs')
      export const path: typeof import('path')
      export const cwd: () => string
    }

These changes ensure the compiler will flag any missing sync methods immediately and align the types with the actual runtime behavior.

🤖 Prompt for AI Agents
In packages/nextjs/src/server/fs/utils.ts around the nodeFsOrThrow definition
(used by packages/nextjs/src/server/keyless-telemetry.ts line 47), the function
is currently untyped and returns any; change its signature to explicitly return
Node’s fs type (i.e., nodeFsOrThrow(): typeof import("fs")) and keep the
existing runtime checks, and add a TypeScript declaration file (e.g.,
packages/nextjs/src/server/fs/utils.d.ts) declaring module '#safe-node-apis'
that exports fs: typeof import("fs"), path: typeof import("path"), and cwd: ()
=> string so callers get proper typings and the compiler will enforce presence
of sync methods.

const flagFilePath = getTelemetryFlagFilePath();
const flagDirectory = dirname(flagFilePath);

// Ensure the directory exists before attempting to write the file
await mkdir(flagDirectory, { recursive: true });
await mkdirSync(flagDirectory, { recursive: true });

const flagData = {
firedAt: new Date().toISOString(),
event: EVENT_KEYLESS_ENV_DRIFT_DETECTED,
};
await writeFile(flagFilePath, JSON.stringify(flagData, null, 2), { flag: 'wx' });
await writeFileSync(flagFilePath, JSON.stringify(flagData, null, 2), { flag: 'wx' });
return true;
} else {
return false;
Expand Down