mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
This commit is contained in:
committed by
GitHub
parent
c50a83a94d
commit
f1a02f602b
@@ -21,6 +21,8 @@
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **fix(startup):** best-effort self-heal for a corrupted Turbopack dev cache on Windows ([#6289](https://github.com/diegosouzapw/OmniRoute/issues/6289)). On Windows, `pnpm dev` can fail at startup when Turbopack `mmap`s a persistent-cache SST file and the OS refuses the mapping (`os error 1455` — "paging file too small"), which Turbopack surfaces as a misleading `Module not found: Can't resolve '@/shared/utils/machine'`. This is a **known upstream Turbopack cache-corruption bug — not our code**. The dev launcher (`scripts/dev/run-next.mjs`) now wraps `nextApp.prepare()` and, when it rejects with that signature (`isTurbopackCacheCorruption` in the new `scripts/dev/turbopackCacheHeal.mjs`), purges `.build/next/**/cache/turbopack` and retries **once** with a clear log. **Caveat — best-effort only:** the corruption often surfaces as a runtime overlay rather than a `prepare()` rejection, so this cannot always intercept it; the reliable remedy remains manually deleting the Turbopack cache dir. Regression guard: `tests/unit/turbopack-cache-heal-6289.test.ts`. (thanks @chirag127)
|
||||
|
||||
- **fix(providers):** qodercli PAT auth no longer fails with `spawn qodercli ENOENT` on Windows ([#6263](https://github.com/diegosouzapw/OmniRoute/issues/6263)) — `spawnQoderCli` spawned the bare `qodercli` name with `shell:false` and an unenriched env, so the npm `.cmd` wrapper under `%APPDATA%\npm` (a user-PATH directory) was never resolved. It now resolves the absolute `.cmd`/`.exe` path through the existing `getCliRuntimeStatus("qoder")` resolver in `src/shared/services/cliRuntime.ts` (memoized), spawns with `shell` when the target is a `.cmd`/`.bat`, and uses the cliRuntime-enriched env (PATH + PATHEXT + APPDATA); the ENOENT error now lists the searched paths plus the `CLI_QODER_BIN` override. End-to-end spawn on a real Windows host is host-only (Hard Rule #18); the path-resolution logic is unit-tested. Regression guard: `tests/unit/qodercli-windows-resolve-6263.test.ts`. (thanks @chirag127)
|
||||
|
||||
- **fix(sse):** the reasoning-token buffer no longer inflates **probe-sized `max_tokens`** ([#6274](https://github.com/diegosouzapw/OmniRoute/issues/6274)) — Claude Code's `/model` capability check sends `max_tokens: 1`, but for a thinking-capable model with a large output cap (e.g. `glm-5.2`) the #3587 headroom heuristic (`max(current + 1000, ceil(current * 1.5))`) rewrote it to `1001` and forwarded that upstream, wasting tokens on a request that was never a genuine reasoning budget. `resolveReasoningBufferedMaxTokens()` (`open-sse/services/reasoningTokenBuffer.ts`) now short-circuits and returns the caller's value verbatim when it is below the new `REASONING_BUFFER_MIN_TRIGGER` (256) threshold — a tiny explicit limit is a probe, not a reasoning request. Real budgets still receive the #3587 headroom unchanged, and the guard runs after the existing capability checks so unknown / non-reasoning models keep returning `null`. Regression guard: `tests/unit/reasoning-token-buffer-6274.test.ts`. (thanks @brightfiscalband)
|
||||
|
||||
@@ -11,6 +11,10 @@ import { createResponsesWsProxy } from "./responses-ws-proxy.mjs";
|
||||
import { ensurePeerStampToken, stampPeerIp } from "./peer-stamp.mjs";
|
||||
import methodGuard from "./http-method-guard.cjs";
|
||||
import { ensureNativeSqlite } from "./ensure-native-sqlite.mjs";
|
||||
import {
|
||||
isTurbopackCacheCorruption,
|
||||
purgeAllTurbopackCaches,
|
||||
} from "./turbopackCacheHeal.mjs";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
const { maybeHandleDisallowedMethod } = methodGuard;
|
||||
@@ -88,17 +92,46 @@ ensurePeerStampToken();
|
||||
if (!useTurbopack) {
|
||||
delete process.env.TURBOPACK;
|
||||
}
|
||||
const nextApp = next({
|
||||
dev,
|
||||
dir: process.cwd(),
|
||||
hostname,
|
||||
port: dashboardPort,
|
||||
turbopack: useTurbopack,
|
||||
webpack: !useTurbopack,
|
||||
});
|
||||
function createNextApp() {
|
||||
return next({
|
||||
dev,
|
||||
dir: process.cwd(),
|
||||
hostname,
|
||||
port: dashboardPort,
|
||||
turbopack: useTurbopack,
|
||||
webpack: !useTurbopack,
|
||||
});
|
||||
}
|
||||
|
||||
let nextApp = createNextApp();
|
||||
|
||||
// Best-effort self-heal for a corrupted Turbopack persistent dev cache (#6289):
|
||||
// on Windows an mmap of an SST cache file can fail ("os error 1455" / paging
|
||||
// file too small), which Turbopack surfaces as a misleading module-resolve
|
||||
// error. This is an UPSTREAM Turbopack cache-corruption bug — not our code.
|
||||
// When `prepare()` rejects with that signature we purge the cache and retry
|
||||
// ONCE. Caveat: the failure often surfaces as a runtime overlay rather than a
|
||||
// `prepare()` rejection, so this cannot always intercept it — the reliable
|
||||
// remedy remains manually deleting `.build/next/**/cache/turbopack`.
|
||||
async function prepareWithHeal() {
|
||||
try {
|
||||
await nextApp.prepare();
|
||||
} catch (error) {
|
||||
const detail = error instanceof Error ? `${error.message}\n${error.stack ?? ""}` : String(error);
|
||||
if (!useTurbopack || !isTurbopackCacheCorruption(detail)) throw error;
|
||||
console.warn(
|
||||
"[Next] Turbopack dev cache looks corrupted (Windows mmap / os error 1455 — known upstream bug). Purging and retrying once…"
|
||||
);
|
||||
const removed = purgeAllTurbopackCaches();
|
||||
for (const dir of removed) console.warn(`[Next] purged Turbopack cache: ${dir}`);
|
||||
nextApp = createNextApp();
|
||||
await nextApp.prepare();
|
||||
console.warn("[Next] Turbopack dev cache purged; startup retry succeeded.");
|
||||
}
|
||||
}
|
||||
|
||||
async function start() {
|
||||
await nextApp.prepare();
|
||||
await prepareWithHeal();
|
||||
|
||||
const requestHandler = nextApp.getRequestHandler();
|
||||
const upgradeHandler = nextApp.getUpgradeHandler();
|
||||
|
||||
74
scripts/dev/turbopackCacheHeal.mjs
Normal file
74
scripts/dev/turbopackCacheHeal.mjs
Normal file
@@ -0,0 +1,74 @@
|
||||
// Best-effort self-heal for a corrupted Turbopack persistent dev cache.
|
||||
//
|
||||
// Context (#6289): on Windows, `pnpm dev` can fail at startup when Turbopack
|
||||
// mmaps a persistent-cache SST file and the OS refuses the mapping
|
||||
// ("os error 1455" — "paging file too small"). Turbopack then surfaces a
|
||||
// misleading `Module not found: Can't resolve '@/shared/utils/machine'`.
|
||||
// This is a known UPSTREAM Turbopack cache-corruption bug — NOT our code.
|
||||
// The reliable remedy is deleting the Turbopack cache dir; this module lets
|
||||
// the dev launcher attempt that automatically once before giving up.
|
||||
//
|
||||
// Pure + side-effect-isolated so it can be unit-tested without booting Next.
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
// Signature of the corrupted-cache failure. Kept intentionally broad because
|
||||
// the same corruption surfaces through several messages (the raw mmap/SST
|
||||
// error, the Windows paging-file error code, and the misleading module-resolve
|
||||
// error emitted by Turbopack's "restore task data" step).
|
||||
const CORRUPTION_SIGNATURE = /restore task data|mmap .*SST|os error 1455|paging file/i;
|
||||
|
||||
/**
|
||||
* True when an error message looks like a corrupted Turbopack persistent cache.
|
||||
* @param {unknown} message
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isTurbopackCacheCorruption(message) {
|
||||
if (message == null) return false;
|
||||
return CORRUPTION_SIGNATURE.test(String(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Candidate Turbopack cache directories for a given Next dist dir. Next has
|
||||
* placed the persistent cache under both `<distDir>/cache/turbopack` and
|
||||
* `<distDir>/dev/cache/turbopack` across versions, so purge both.
|
||||
* @param {string} [distDir]
|
||||
* @param {string} [cwd]
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function turbopackCacheDirs(
|
||||
distDir = process.env.NEXT_DIST_DIR || ".build/next",
|
||||
cwd = process.cwd()
|
||||
) {
|
||||
const base = path.isAbsolute(distDir) ? distDir : path.join(cwd, distDir);
|
||||
return [
|
||||
path.join(base, "cache", "turbopack"),
|
||||
path.join(base, "dev", "cache", "turbopack"),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively remove a single Turbopack cache directory.
|
||||
* @param {string} dir
|
||||
* @returns {boolean} true if the directory existed and was removed
|
||||
*/
|
||||
export function purgeTurbopackCache(dir) {
|
||||
if (!dir || !fs.existsSync(dir)) return false;
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge every candidate Turbopack cache directory for the given dist dir.
|
||||
* @param {string} [distDir]
|
||||
* @param {string} [cwd]
|
||||
* @returns {string[]} the directories that existed and were removed
|
||||
*/
|
||||
export function purgeAllTurbopackCaches(distDir, cwd) {
|
||||
const removed = [];
|
||||
for (const dir of turbopackCacheDirs(distDir, cwd)) {
|
||||
if (purgeTurbopackCache(dir)) removed.push(dir);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
76
tests/unit/turbopack-cache-heal-6289.test.ts
Normal file
76
tests/unit/turbopack-cache-heal-6289.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import {
|
||||
isTurbopackCacheCorruption,
|
||||
purgeTurbopackCache,
|
||||
turbopackCacheDirs,
|
||||
} from "../../scripts/dev/turbopackCacheHeal.mjs";
|
||||
|
||||
// Regression for #6289: on Windows, `pnpm dev` fails when Turbopack mmaps a
|
||||
// corrupted persistent-cache SST file ("os error 1455" / paging file too
|
||||
// small), surfaced as a misleading `Module not found: Can't resolve
|
||||
// '@/shared/utils/machine'`. The launcher (scripts/dev/run-next.mjs) uses these
|
||||
// pure helpers to detect that signature and purge the Turbopack dev cache
|
||||
// before retrying once. This test covers ONLY the pure logic — reproducing the
|
||||
// real mmap failure is host-only and out of scope.
|
||||
|
||||
test("isTurbopackCacheCorruption matches the corrupted-cache signatures", () => {
|
||||
const matching = [
|
||||
"failed to mmap SST file .build/next/cache/turbopack/data.sst",
|
||||
"Backend error: Storage restore task data failed",
|
||||
"memory map failed: os error 1455",
|
||||
"The paging file is too small for this operation to complete. (os error 1455)",
|
||||
"Module not found: os error 1455 while trying to restore task data",
|
||||
];
|
||||
for (const msg of matching) {
|
||||
assert.equal(isTurbopackCacheCorruption(msg), true, `should match: ${msg}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("isTurbopackCacheCorruption returns false for unrelated errors", () => {
|
||||
const unrelated = [
|
||||
"Module not found: Can't resolve '@/shared/utils/machine'",
|
||||
"EADDRINUSE: address already in use :::20128",
|
||||
"TypeError: Cannot read properties of undefined",
|
||||
"",
|
||||
null,
|
||||
undefined,
|
||||
];
|
||||
for (const msg of unrelated) {
|
||||
assert.equal(isTurbopackCacheCorruption(msg), false, `should NOT match: ${String(msg)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("turbopackCacheDirs resolves both known cache layouts under the dist dir", () => {
|
||||
const cwd = "/tmp/omniroute-test";
|
||||
const dirs = turbopackCacheDirs(".build/next", cwd);
|
||||
assert.deepEqual(dirs, [
|
||||
path.join(cwd, ".build/next", "cache", "turbopack"),
|
||||
path.join(cwd, ".build/next", "dev", "cache", "turbopack"),
|
||||
]);
|
||||
});
|
||||
|
||||
test("purgeTurbopackCache removes an existing cache/turbopack dir", () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "tp-cache-heal-"));
|
||||
const cacheDir = path.join(tmp, "cache", "turbopack");
|
||||
fs.mkdirSync(cacheDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(cacheDir, "data.sst"), "corrupt");
|
||||
assert.equal(fs.existsSync(cacheDir), true);
|
||||
|
||||
const removed = purgeTurbopackCache(cacheDir);
|
||||
|
||||
assert.equal(removed, true);
|
||||
assert.equal(fs.existsSync(cacheDir), false);
|
||||
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("purgeTurbopackCache is a no-op (returns false) when the dir is absent", () => {
|
||||
const missing = path.join(os.tmpdir(), "tp-cache-heal-missing-does-not-exist");
|
||||
assert.equal(fs.existsSync(missing), false);
|
||||
assert.equal(purgeTurbopackCache(missing), false);
|
||||
});
|
||||
Reference in New Issue
Block a user