Skip to content

feat(deps): upgrade puppeteer to 25, axios to 1.19, better-sqlite3 to 13; require Node >= 22 - #110

Merged
djannot merged 4 commits into
mainfrom
denis-issue-109-dep-upgrades
Aug 19, 2026
Merged

feat(deps): upgrade puppeteer to 25, axios to 1.19, better-sqlite3 to 13; require Node >= 22#110
djannot merged 4 commits into
mainfrom
denis-issue-109-dep-upgrades

Conversation

@djannot

@djannot djannot commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Final combined dependency upgrade for #109, superseding the remaining Snyk PRs (#86, #97, #90 — will close them once this merges).

Dependency changes

Package From To Why
puppeteer ^24.36.1 ^25.8.0 Drops vulnerable extract-zip (SNYK-JS-EXTRACTZIP-17660777 has no fixed release; v25 replaced it with modern-tar)
axios ^1.6.2 ^1.19.0 Clears every advisory flagged up to 1.17.x (npm audit flags 1.0.0 – 1.17.0)
js-yaml ^4.1.0 ^4.3.1 As planned in #109
better-sqlite3 (root + mcp) ^11.x ^13.0.3 11.x cannot compile on Node ≥ 26 (removed V8 APIs)

Node requirement

Puppeteer 25 is ESM-only and requires Node ≥ 22, so:

  • engines.node >= 22 declared in package.json (hence the minor version bump to 2.15.0)
  • Docker image moves from node:20-slim (EOL, incompatible with puppeteer 25) to node:26-slim

Code adaptations for puppeteer 25

  • puppeteer.executablePath() is now async → resolveBrowserExecutablePath awaits it
  • browser.isConnected() was removed → .connected property

mcp build repair

The mcp package has not compiled since #62 bumped @modelcontextprotocol/sdk to 1.25.3 (no CI to catch it):

  • capabilities moved to McpServer's options argument (no longer valid in server info)
  • zod bumped to ^3.25.0 (SDK requires ^3.25 || ^4) with an npm overrides entry so the SDK no longer gets a nested zod 4 next to the app's zod 3 (that mismatch caused the TS2589 errors)
  • @qdrant/js-client-rest pinned to ^1.16.2 — 1.17+ drops the search method server.ts relies on (follow-up: migrate to the query API)

Test fixes

Puppeteer 25's async executablePath() does real I/O; the four crawlWebsite tests that run it under fake timers hung (the retry timer was only scheduled after the timer advance had finished) — those tests now stub executablePath, and the crawlWebsite afterEach restores real timers so a single failing test can no longer leak frozen timers into the remaining tests of the block.

Verification

On Node 26.7.0: root tsc build ✅, mcp build ✅, full vitest suite 723 passed / 8 skipped ✅. npm audit: axios/puppeteer/extract-zip advisories cleared; mcp reports 0 vulnerabilities. Remaining root audit findings (~20, in @mozilla/readability, @xmldom/xmldom, fast-xml-parser, nanoid, esbuild-via-vitest…) are the separate final CVE check from #109.

Part of #109

🤖 Generated with Claude Code

Also: qdrant client Node 26 compatibility + silent embedding failures

  • @qdrant/js-client-rest → ^1.19.0 (root): the 1.13 client passes an undici-5 Agent to Node's global fetch; on Node 26 (undici 7) every request failed with fetch failed (InvalidArgumentError: invalid onError method). Verified against a real Qdrant container from the built image.
  • Embedding failures no longer lose data silently: a 401 from the embeddings API used to be swallowed per chunk while processChunksForUrl/processIssue had already deleted the item's existing chunks — destroying data, storing nothing, advancing sync markers, and finishing green. Now all chunks are embedded before old ones are deleted, a FatalEmbeddingError (401/403) aborts the source immediately, and transient failures mark the item failed so sync markers aren't advanced and the next run retries. 6 new tests.

… 13; require Node >= 22

Final dependency upgrade for the security cleanup in #109:

- puppeteer ^25.8.0: drops the vulnerable extract-zip dependency
  (SNYK-JS-EXTRACTZIP-17660777 has no fixed release; puppeteer 25 replaced
  extract-zip with modern-tar). Code adapted to the v25 API: executablePath()
  is now async, browser.isConnected() became the .connected property.
- axios ^1.19.0: clears all advisories flagged up to 1.17.x.
- js-yaml ^4.3.1 (as planned in #109).
- better-sqlite3 ^13.0.3 in root and mcp: 11.x cannot compile on Node >= 26
  (uses removed V8 APIs).
- Node: puppeteer 25 is ESM-only and needs Node >= 22, so package.json now
  declares engines.node >= 22 and the Docker image moves from node:20-slim
  (EOL) to node:26-slim.
- mcp: repair the build broken since #62 bumped the MCP SDK to 1.25.3 —
  capabilities moved to McpServer's options argument, zod ^3.25.0 with an
  npm override so the SDK no longer nests zod 4 next to the app's zod 3,
  @qdrant/js-client-rest pinned to ^1.16.2 (1.17+ drops the search method
  the server relies on).
- tests: stub puppeteer.executablePath in the four crawlWebsite tests that
  run it under fake timers (it does real I/O since v25 and the retry timer
  was scheduled after the timer advance, hanging the crawl); restore real
  timers in the crawlWebsite afterEach so a failing test can no longer leak
  frozen timers into the rest of the block.

Verified on Node 26.7.0: root build, mcp build, full vitest suite
(723 passed / 8 skipped). npm audit: axios/puppeteer/extract-zip advisories
cleared; mcp reports 0 vulnerabilities.

Signed-off-by: Denis Jannot <denis.jannot@solo.io>
… base)

The package was orphaned and dropped from trixie; it only provided Arabic
display fonts, which doc2vec never needs — pages are text-extracted, not
rendered to images.

Signed-off-by: Denis Jannot <denis.jannot@solo.io>
…atibility

The 1.13 client builds an Agent from its bundled undici 5 and passes it as
the dispatcher to the global fetch — on Node 26 that's undici 7, whose
dispatch-handler protocol differs, so every request dies with
'fetch failed' (cause: InvalidArgumentError: invalid onError method).
1.19.0 ships undici 7.29 and works against a real Qdrant from the image
(verified with docker). The root sync code doesn't use the search API that
1.17 removed, so unlike mcp it can take the latest client.

Signed-off-by: Denis Jannot <denis.jannot@solo.io>
…mbedding fails

A rejected embedding request (e.g. an invalid OpenAI API key) was swallowed
at every layer: createEmbeddings caught all errors and returned [], callers
logged 'Embedding failed for chunk' per chunk and moved on, and both
processChunksForUrl and the GitHub processIssue path deleted the item's
existing chunks BEFORE embedding — so a bad key deleted stored chunks,
stored nothing back, advanced the sync markers (ETag/lastmod, last-run
date), and the run finished green.

Now:
- createEmbeddings throws FatalEmbeddingError on 401/403 — an auth failure
  is permanent, so the source aborts instead of degrading every chunk into
  a logged-and-skipped failure. Transient errors still return [].
- FatalEmbeddingError is rethrown through every per-item catch (website
  page callback + crawl loop, GitHub issue, Zendesk ticket, local dir, S3,
  code file), so a bad key fails the source on the first chunk.
- processChunksForUrl and processIssue embed all chunks BEFORE deleting
  the old ones; on any embedding failure the existing chunks are kept and
  the item fails: websites don't store ETag/lastmod (page retried next
  run), GitHub counts it toward failedChunks (last-run date not advanced),
  Zendesk counts failedTickets.

Tests: 6 new cases covering the fatal 401/403 path, transient-failure
tolerance, and that existing chunks are never deleted when embedding fails.

Signed-off-by: Denis Jannot <denis.jannot@solo.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