Skip to content

Use node:26-alpine for docker images - #3055

Merged
ianhe8x merged 3 commits into
mainfrom
fix/docker-node-24
Oct 29, 2026
Merged

ianhe8x merged 3 commits into
mainfrom
fix/docker-node-24

Conversation

@ianhe8x

@ianhe8x ianhe8x commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The Node-to-docker run for node/6.5.0 failed in both jobs (https://github.com/subquery/subql/actions/runs/40824622723):

#19 0.817 /bin/sh: yarn: not found
ERROR: process "/bin/sh -c apk add --no-cache tini curl git && tar -xzvf /app.tgz --strip 1 && rm /app.tgz && yarn install --production && ..." did not complete successfully: exit code: 127

node:lts-alpine moved to Node 26 on 2026-10-28 (it now has the same digest as node:26-alpine), and the Node 26 image doesn't include yarn. The builder stage still works because scripts/build.sh installs yarn with npm, but the runtime stage used the yarn that came with the image.

Change

  • Both stages of the node and query Dockerfiles use node:26-alpine instead of node:lts-alpine, so the next LTS change can't break the images without a code change.
  • The runtime stage installs yarn with npm install -g --force yarn@latest, the same way scripts/build.sh does in the builder stage.
  • The query Dockerfile had the same problem, so the next query release would have failed the same way.
  • The images move from Node 24 to Node 26. The published images ran Node 24 (onfinality/subql-node:v6.4.6 has v24.11.1, onfinality/subql-query:latest has v24.22.1).

Testing

Both images were built locally (linux/arm64) from this branch:

  • Both run Node v26.13.0 with yarn 1.22.22. The node image has @subql/node 6.5.0 with @subql/node-core 19.4.0.
  • The node image indexed a minimal Polkadot project (one block handler that saves an entity, run in the vm2 sandbox) from block 33640000 into Postgres 16. 36 consecutive blocks were stored. The only errors were 429 rate limits from the public RPC, which it retried.
  • The query image served that schema. blockEntities and _metadata returned the indexed blocks (indexerNodeVersion 6.5.0, queryNodeVersion 2.25.0).

Release

npm 6.5.0 and the GitHub release are already published. Only the docker images are missing. Re-running the failed run won't help, because it checks out the node/6.5.0 tag, which has the old Dockerfile. So the images are built from this branch with a Node-to-docker workflow_dispatch (isLatest=true) before merging. After the merge, the prerelease bumps packages/node to 6.5.1-0, so a dispatch from main would tag the image with that version.

Summary by CodeRabbit

  • Chores
    • Docker images for the Node and query packages now use Node.js 26 instead of the previous LTS Alpine image. Yarn is installed in the production images, where it is available alongside production dependency installation.

node:lts-alpine now points to Node 26, which no longer ships yarn, so the
runtime stage failed at yarn install --production. The published images
run Node 24.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T07:34:16.532016Z 39139f4 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d21032c5-5eb7-4b9d-afff-0fdae37be9ea
Commits

Reviewing files that changed from the base of the PR and between 780213c and d74d818.

Files selected for processing: 4
  • packages/node/CHANGELOG.md
  • packages/node/Dockerfile
  • packages/query/CHANGELOG.md
  • packages/query/Dockerfile
Files skipped from review as they are similar to previous changes: 2
  • packages/query/CHANGELOG.md
  • packages/node/CHANGELOG.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Walkthrough

The node and query Dockerfiles now use node:26-alpine in both build stages. Their production stages install yarn@latest globally with --force. The package changelogs record these updates.

Changes: Docker image updates

Layer / File(s) Summary
Update Docker stages and changelogs
packages/node/Dockerfile, packages/query/Dockerfile, packages/node/CHANGELOG.md, packages/query/CHANGELOG.md
Both Dockerfiles use node:26-alpine in their builder and production stages. Each production stage installs yarn@latest globally with --force. Both changelogs record the changes.

Priority: Normal

Estimated code review effort: 2 (Simple) | ~5 minutes

Change: Bug fix

Merge Risk: Minimal · up to d74d8

The images use Node 26 rather than the described Node 24 pin. Yarn installation is provided in both stages, and no actionable merge-blocking defect was identified; normal build and compatibility checks remain necessary.

Pre-merge checks: Passed 4
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title names the main change, which moves the node and query Docker images from node:lts-alpine to node:26-alpine. The PR objectives describe PR #3055 as pinning the images to node:24-alpine, which…
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.
Full details: Title check

Explanation

The title names the main change, which moves the node and query Docker images from node:lts-alpine to node:26-alpine. The PR objectives describe PR #3055 as pinning the images to node:24-alpine, which conflicts with the title and the file summaries. The changeset as summarized uses node:26-alpine and adds a global yarn install, so the title matches the changes, but the stated objective does not.

Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

The Node 26 image no longer ships yarn. Pin the base image to an explicit
major version instead of lts-alpine, so a future LTS change can't break
the images.
@ianhe8x ianhe8x changed the title Pin docker images to node:24-alpine Use node:26-alpine for docker images Oct 29, 2026
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Coverage report for .

Caution

Test run failed

St.❔
Category Percentage Covered / Total
🟢 Statements
87.01% (+16.9% 🔼)
70758/81320
🟢 Branches
80.75% (+2.45% 🔼)
3338/4134
🟡 Functions
65.24% (+2.74% 🔼)
1250/1916
🟢 Lines
87.01% (+16.9% 🔼)
70758/81320

⚠️ Details were not displayed: the report size has exceeded the limit.

Test suite run failed

Failed tests: 28/739. Failed suites: 8/116.
  ● Dictionary V1 › coreDictionary › set startHeight of this dictionary

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › coreDictionary › validateChainMeta and useDictionary

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › coreDictionary › validate dictionary with a height

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › coreDictionary › able to build queryEntryMap

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › coreDictionary › can use scoped dictionary query

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › coreDictionary › able to getDicitonaryQueryEntries

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › get metadata

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › init metadata and get metadata

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › return dictionary query result

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › should return undefined startblock height greater than dictionary last processed height

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Dictionary V1 › should use metadata last process height at end of query height

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"2db0b0156ff6311611089e2cda73fe48"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:58:18)

  ● Individual dictionary V1 test › return undefined when dictionary api failed

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"4423c35113f7674937b0c56a20e7ab69"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:186:18)

  ● Individual dictionary V1 test › limits the dictionary query to that block range

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"0471af6538ec0037aa64704724e2da4c"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:186:18)

  ● Individual dictionary V1 test › test query the correct range

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"359ec474e993d80f91238b3e624e750a"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:186:18)

  ● Individual dictionary V1 test › use minimum value of event/extrinsic returned block as batch end block

    DictionaryQueryError: Dictionary returned HTTP 503 without data: {
      "message":"failure to get a peer from the ring-balancer",
      "request_id":"fa27bf18d456fe7988f92c643e4b826a"
    }

      94 |     const data = body?.data;
      95 |     if (!response.ok || typeof data !== 'object' || data === null || Array.isArray(data)) {
    > 96 |       throw new DictionaryQueryError(
         |             ^
      97 |         `Dictionary returned HTTP ${response.status} without data: ${text.slice(0, 500)}`,
      98 |         response.status,
      99 |         [],

      at TestDictionaryV1.query (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:96:13)
      at TestDictionaryV1.init (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.ts:110:20)
      at prepareDictionary (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:49:3)
      at Object.<anonymous> (packages/node-core/src/indexer/dictionary/v1/dictionaryV1.spec.ts:186:18)


  ● Load chain type hasher › should use new hasher function, types hasher string should be replaced with function

    thrown: "Exceeded timeout of 90000 ms for a test.
    Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

      509 |   };
      510 |
    > 511 |   it('should use new hasher function, types hasher string should be replaced with function', async () => {
          |   ^
      512 |     const apiService = await prepareApiService();
      513 |     const api = apiService.api;
      514 |     expect(typeof (api as any)._options.typesBundle.spec.gargantua.hasher).toBe(

      at packages/node/src/indexer/api.service.test.ts:511:3
      at Object.<anonymous> (packages/node/src/indexer/api.service.test.ts:447:1)


  ● Substrate DictionaryService › should return all specVersion

    expect(received).toBeGreaterThan(expected)

    Matcher error: received value must be a number or bigint

    Received has value: undefined

      63 |     const specVersions = await dictionaryService.getSpecVersions();
      64 |
    > 65 |     expect(specVersions?.length).toBeGreaterThan(0);
         |                                  ^
      66 |     dictionaryService.onApplicationShutdown();
      67 |   }, 50000);
      68 | });

      at Object.<anonymous> (packages/node/src/indexer/dictionary/v1/substrateDictionaryV1.spec.ts:65:34)


  ● AutoQueue › resumes after flushing

    expect(received).toEqual(expected) // deep equality

    - Expected  - 0
    + Received  + 2

    @@ -1,8 +1,10 @@
      Array [
        1,
        2,
    +   3,
    +   4,
        11,
        12,
        13,
        14,
        15,

      192 |     );
      193 |
    > 194 |     expect(results).toEqual([1, 2, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]);
          |                     ^
      195 |   });
      196 |
      197 |   it('resolves the isIdle promise when the queue becomes idle', async () => {

      at Object.<anonymous> (packages/node-core/src/utils/queues/autoQueue.spec.ts:194:21)


  ● substrate utils › return undefined if no timestamp set extrinsic

    RpcError: 4003: Client error: Api called for an unknown Block: State already discarded for 0xf1cf9022c7ebb34b162d5b5e34e705a5a740b2d0ecc1009fb89023e62a488108

      150 |       formatted_error = new LargeResponseError(e);
      151 |     } else {
    > 152 |       formatted_error = new ApiConnectionError(
          |                         ^
      153 |         e.name,
      154 |         e.message,
      155 |         ApiErrorType.Default,

      at ApiPromiseConnection.handleError (packages/node/src/indexer/apiPromise.connection.ts:152:25)
      at packages/node/src/utils/substrate.ts:312:32
      at getBlockByHeight (packages/node/src/utils/substrate.ts:308:17)
      at Object.<anonymous> (packages/node/src/utils/substrate.test.ts:102:20)

  ● substrate utils › return defined if no timestamp set extrinsic

    RpcError: 4003: Client error: Api called for an unknown Block: State already discarded for 0x96ee06f85333baba93e16da4384978e2c1ac4bfed789446b975ffda894da90be

      150 |       formatted_error = new LargeResponseError(e);
      151 |     } else {
    > 152 |       formatted_error = new ApiConnectionError(
          |                         ^
      153 |         e.name,
      154 |         e.message,
      155 |         ApiErrorType.Default,

      at ApiPromiseConnection.handleError (packages/node/src/indexer/apiPromise.connection.ts:152:25)
      at packages/node/src/utils/substrate.ts:312:32
      at getBlockByHeight (packages/node/src/utils/substrate.ts:308:17)
      at Object.<anonymous> (packages/node/src/utils/substrate.test.ts:110:20)


  ● etherscan api › Deployment information › can get the deploy height of a contract

    Etherscan API error: Free API access is not supported for this chain. Please upgrade your api plan for full chain coverage. https://etherscan.io/apis

      24 |       return undefined;
      25 |     }
    > 26 |     throw new Error(`Etherscan API error: ${data.result}`);
         |           ^
      27 |   }
      28 |
      29 |   return data.result;

      at runRequest (packages/cli/src/utils/etherscan.ts:26:11)
      at fetchContractDeployHeight (packages/cli/src/utils/etherscan.ts:69:18)
      at Object.<anonymous> (packages/cli/src/utils/etherscan.spec.ts:41:22)


  ● Integration test - Publish › overwrites any existing CID files

    Failed to generate manifest from typescript /tmp/JNtD5x/mocked_starter/project.ts, Failed to build /tmp/JNtD5x/mocked_starter/project.ts: TypeError: Cannot read properties of undefined (reading 'fileExists'), undefined

      55 |   await codegen(root, manifests);
      56 | }
    > 57 |
         | ^
      58 | export default class Codegen extends Command {
      59 |   static description = 'Generate entity types from the GraphQL schema and contract interfaces';
      60 |   static args = zodToArgs(codegenInputs);

      at Object.error (node_modules/@oclif/core/lib/errors/error.js:44:15)
      at Codegen.error (node_modules/@oclif/core/lib/command.js:216:23)
      at Codegen.run (packages/cli/src/commands/codegen/index.ts:57:18)
      at async Codegen._run (node_modules/@oclif/core/lib/command.js:182:22)
      at async installAndBuild (packages/cli/src/createProject.fixtures.ts:75:5)
      at async createTestProject (packages/cli/src/createProject.fixtures.ts:84:5)
      at async Object.<anonymous> (packages/cli/src/commands/publish.test.ts:17:22)

  ● Integration test - Publish › create ipfsCID file stored in local with dictiory path

    Failed to generate manifest from typescript /tmp/JNtD5x/mocked_starter/project.ts, Failed to build /tmp/JNtD5x/mocked_starter/project.ts: TypeError: Cannot read properties of undefined (reading 'fileExists'), undefined

      55 |   await codegen(root, manifests);
      56 | }
    > 57 |
         | ^
      58 | export default class Codegen extends Command {
      59 |   static description = 'Generate entity types from the GraphQL schema and contract interfaces';
      60 |   static args = zodToArgs(codegenInputs);

      at Object.error (node_modules/@oclif/core/lib/errors/error.js:44:15)
      at Codegen.error (node_modules/@oclif/core/lib/command.js:216:23)
      at Codegen.run (packages/cli/src/commands/codegen/index.ts:57:18)
      at async Codegen._run (node_modules/@oclif/core/lib/command.js:182:22)
      at async installAndBuild (packages/cli/src/createProject.fixtures.ts:75:5)
      at async createTestProject (packages/cli/src/createProject.fixtures.ts:84:5)
      at async Object.<anonymous> (packages/cli/src/commands/publish.test.ts:17:22)

  ● Integration test - Publish › file name consistent with manifest file name, if -f <manifest path> is used

    Failed to generate manifest from typescript /tmp/JNtD5x/mocked_starter/project.ts, Failed to build /tmp/JNtD5x/mocked_starter/project.ts: TypeError: Cannot read properties of undefined (reading 'fileExists'), undefined

      55 |   await codegen(root, manifests);
      56 | }
    > 57 |
         | ^
      58 | export default class Codegen extends Command {
      59 |   static description = 'Generate entity types from the GraphQL schema and contract interfaces';
      60 |   static args = zodToArgs(codegenInputs);

      at Object.error (node_modules/@oclif/core/lib/errors/error.js:44:15)
      at Codegen.error (node_modules/@oclif/core/lib/command.js:216:23)
      at Codegen.run (packages/cli/src/commands/codegen/index.ts:57:18)
      at async Codegen._run (node_modules/@oclif/core/lib/command.js:182:22)
      at async installAndBuild (packages/cli/src/createProject.fixtures.ts:75:5)
      at async createTestProject (packages/cli/src/createProject.fixtures.ts:84:5)
      at async Object.<anonymous> (packages/cli/src/commands/publish.test.ts:17:22)


  ● Cli publish › should upload appropriate project to IPFS

    Failed to generate manifest from typescript /tmp/zDQYID/mocked_starter/project.ts, Failed to build /tmp/zDQYID/mocked_starter/project.ts: TypeError: Cannot read properties of undefined (reading 'fileExists'), undefined

      55 |   await codegen(root, manifests);
      56 | }
    > 57 |
         | ^
      58 | export default class Codegen extends Command {
      59 |   static description = 'Generate entity types from the GraphQL schema and contract interfaces';
      60 |   static args = zodToArgs(codegenInputs);

      at Object.error (node_modules/@oclif/core/lib/errors/error.js:44:15)
      at Codegen.error (node_modules/@oclif/core/lib/command.js:216:23)
      at Codegen.run (packages/cli/src/commands/codegen/index.ts:57:18)
      at async Codegen._run (node_modules/@oclif/core/lib/command.js:182:22)
      at async installAndBuild (packages/cli/src/createProject.fixtures.ts:75:5)
      at async createTestProject (packages/cli/src/createProject.fixtures.ts:84:5)
          at async Promise.all (index 0)
      at async Object.<anonymous> (packages/cli/src/controller/publish-controller.spec.ts:18:21)

  ● Cli publish › convert to deployment and removed descriptive field

    Failed to generate manifest from typescript /tmp/zDQYID/mocked_starter/project.ts, Failed to build /tmp/zDQYID/mocked_starter/project.ts: TypeError: Cannot read properties of undefined (reading 'fileExists'), undefined

      55 |   await codegen(root, manifests);
      56 | }
    > 57 |
         | ^
      58 | export default class Codegen extends Command {
      59 |   static description = 'Generate entity types from the GraphQL schema and contract interfaces';
      60 |   static args = zodToArgs(codegenInputs);

      at Object.error (node_modules/@oclif/core/lib/errors/error.js:44:15)
      at Codegen.error (node_modules/@oclif/core/lib/command.js:216:23)
      at Codegen.run (packages/cli/src/commands/codegen/index.ts:57:18)
      at async Codegen._run (node_modules/@oclif/core/lib/command.js:182:22)
      at async installAndBuild (packages/cli/src/createProject.fixtures.ts:75:5)
      at async createTestProject (packages/cli/src/createProject.fixtures.ts:84:5)
          at async Promise.all (index 0)
      at async Object.<anonymous> (packages/cli/src/controller/publish-controller.spec.ts:18:21)

  ● Cli publish › convert js object to JSON object

    Failed to generate manifest from typescript /tmp/zDQYID/mocked_starter/project.ts, Failed to build /tmp/zDQYID/mocked_starter/project.ts: TypeError: Cannot read properties of undefined (reading 'fileExists'), undefined

      55 |   await codegen(root, manifests);
      56 | }
    > 57 |
         | ^
      58 | export default class Codegen extends Command {
      59 |   static description = 'Generate entity types from the GraphQL schema and contract interfaces';
      60 |   static args = zodToArgs(codegenInputs);

      at Object.error (node_modules/@oclif/core/lib/errors/error.js:44:15)
      at Codegen.error (node_modules/@oclif/core/lib/command.js:216:23)
      at Codegen.run (packages/cli/src/commands/codegen/index.ts:57:18)
      at async Codegen._run (node_modules/@oclif/core/lib/command.js:182:22)
      at async installAndBuild (packages/cli/src/createProject.fixtures.ts:75:5)
      at async createTestProject (packages/cli/src/createProject.fixtures.ts:84:5)
          at async Promise.all (index 0)
      at async Object.<anonymous> (packages/cli/src/controller/publish-controller.spec.ts:18:21)

  ● Cli publish › Get directory CID from multi-chain project

    Failed to generate manifest from typescript /tmp/zDQYID/mocked_starter/project.ts, Failed to build /tmp/zDQYID/mocked_starter/project.ts: TypeError: Cannot read properties of undefined (reading 'fileExists'), undefined

      55 |   await codegen(root, manifests);
      56 | }
    > 57 |
         | ^
      58 | export default class Codegen extends Command {
      59 |   static description = 'Generate entity types from the GraphQL schema and contract interfaces';
      60 |   static args = zodToArgs(codegenInputs);

      at Object.error (node_modules/@oclif/core/lib/errors/error.js:44:15)
      at Codegen.error (node_modules/@oclif/core/lib/command.js:216:23)
      at Codegen.run (packages/cli/src/commands/codegen/index.ts:57:18)
      at async Codegen._run (node_modules/@oclif/core/lib/command.js:182:22)
      at async installAndBuild (packages/cli/src/createProject.fixtures.ts:75:5)
      at async createTestProject (packages/cli/src/createProject.fixtures.ts:84:5)
          at async Promise.all (index 0)
      at async Object.<anonymous> (packages/cli/src/controller/publish-controller.spec.ts:18:21)

Report generated by 🧪jest coverage report action from d74d818

@ianhe8x
ianhe8x merged commit 61dbe97 into main Oct 29, 2026
5 of 6 checks passed
@ianhe8x
ianhe8x deleted the fix/docker-node-24 branch October 29, 2026 07:50
ianhe8x added a commit to subquery/subql-ethereum that referenced this pull request Oct 30, 2026
* ci: replace the revoked REPO_TOKEN with a short-lived GitHub App token

REPO_TOKEN was revoked after it leaked, so Prerelease Publish fails at
checkout. This ports subquery#3053.

- Read-only checkouts (node-docker, benchmark) use the default token.
- Pushing the prerelease commit to main and creating GitHub releases use
  a token minted from the subql-release-bot GitHub App. The token is
  created after install, build and publish, and is passed only to the
  push and release steps, so third-party install and build scripts never
  see a write credential. Releases made with it still trigger the Docker
  and Discord workflows, unlike ones made with GITHUB_TOKEN.
- Prerelease checkout no longer persists credentials, and the commit and
  push are explicit git commands instead of EndBug/add-and-commit.
- pre-ci passes the head commit message through env instead of
  interpolating it into the script, which allowed shell injection in a
  job with id-token: write.

* Use node:26-alpine for the docker image

node:lts-alpine moved to Node 26 on 2026-10-28. Pin the major version so
the next LTS change can't silently change the base image, as
subquery#3055 did.

* ci: check the release app before publishing, and push in a separate step

- The prerelease job publishes to npm before it pushes the version bump.
  If the app couldn't get a token, the prerelease would be on npm while
  main kept the old version, and the next prerelease would fail on the
  same version. A separate job now creates a token first, and the
  prerelease job waits for it.
- git commit runs the husky pre-commit hook (lint-staged), so the commit
  step no longer has the token. The token is created after the commit,
  and only the push step gets it.
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