nodejs.org icon indicating copy to clipboard operation
nodejs.org copied to clipboard

Node 24.x not yet marked as LTS ("Krypton") in index.json (website lists v24.10.0 as Latest LTS)

Open MinaFayez9 opened this issue 3 months ago • 29 comments

Version

Not applicable – issue concerns Node.js distribution metadata, not runtime behavior.

Platform

All platforms (observed on Windows, but affects all because index.json is global).

Subsystem

Release / Distribution metadata

What steps will reproduce the bug?

  1. Visit https://nodejs.org/dist/index.json
  2. Search for any entries for version v24.x.x → all of them have "lts": false.
  3. Visit https://nodejs.org – it lists v24.10.0 as “Latest LTS”.
  4. Tools that rely on index.json (Volta, nvm, fnm, etc.) still treat Node 22 ("Jod") as the current LTS.

How often does it reproduce? Is there a required condition?

Always, until the index.json is updated to include "lts": "Krypton" for Node 24 entries.

What is the expected behavior? Why is that the expected behavior?

When Node 24 is promoted to LTS (codename "Krypton"), each v24.x entry in index.json should have an "lts" field set to "Krypton" rather than false.

Tools and automation use this field to determine the latest LTS line, so keeping it accurate ensures consistency between nodejs.org and the dist metadata.

What do you see instead?

All v24.x entries in index.json currently show "lts": false, while the homepage lists v24.10.0 as “Latest LTS”. The most recent version marked as LTS in index.json is still 22.21.0 ("Jod").

Additional information

Date checked: 2025-10-28

Example entry from https://nodejs.org/dist/index.json: { "version": "v24.10.0", "date": "2025-10-14", "lts": false }

Expected: { "version": "v24.10.0", "date": "2025-10-14", "lts": "Krypton" }

This discrepancy causes tools such as Volta, nvm, and fnm — which rely on index.json to determine the current LTS — to continue installing Node 22 instead of 24.

This seems like a metadata lag during the LTS promotion. Just filing it here in case the release metadata hasn’t yet been updated.

Thanks for maintaining Node.js and for your work on the release process 🙏

MinaFayez9 avatar Oct 28 '25 05:10 MinaFayez9

This is a bug with the website. The first v24 LTS will be v24.11.0 (to be released today)

targos avatar Oct 28 '25 06:10 targos

Duplicate of https://github.com/nodejs/nodejs.org/issues/7153?

aduh95 avatar Oct 28 '25 08:10 aduh95

@aduh95

Thanks for alerting me to this issue! I deleted my comment in https://github.com/nodejs/node/pull/60414 to avoid any confusion (it was already set to off-topic).

MikeMcC399 avatar Oct 28 '25 10:10 MikeMcC399

As of right now, the website is extremely data-driven, and we have found a number of places where missing data (ie: missing release overlaps, postponed or faulty releases, etc.) cause outages, only for it to correct once backing data is available.

Examples: https://github.com/nodejs/nodejs.org/issues/8217 and https://github.com/nodejs/nodejs.org/issues/8248

We should look for ways to hot-wire the behavior, such as a site.json override that allows us to force through LTS, Latest values.

Anything we do would impose it's own temporal constraints...to add and then remove as releases become available.

bmuenzenmeyer avatar Oct 28 '25 11:10 bmuenzenmeyer

I've said this several times but it seems my comment is always ignored: the source of truth is https://nodejs.org/download/release/index.json. You can stay data-driven if you use that.

targos avatar Oct 28 '25 11:10 targos

fair - for what it's worth, we use https://github.com/cutenode/nodevu/tree/main/core - i dont recall the origin of that decision. I am we can get to the bottom of it

bmuenzenmeyer avatar Oct 28 '25 11:10 bmuenzenmeyer

In particular, curl https://nodejs.org/download/release/index.json | jq first always returns the latest Current, and curl https://nodejs.org/download/release/index.json | jq 'map(select(.lts)) | first' always returns the latest LTS.

aduh95 avatar Oct 28 '25 11:10 aduh95

It's one of those unfortunate types of issues that is wrong for a very short amount of time, but when naturally lots of eyeballs are drawn to the site.

I started looking into this. There are some reasons to use the nodevu logic. I think one solve would be to cross-reference it with the index.json to never OVERPROMISE release information that should be present based on a simple date comparison

bmuenzenmeyer avatar Oct 28 '25 12:10 bmuenzenmeyer

What do we actually need nodevu's additional support data for? I am very much in favor of just relying on our our dist file with the LTS flag it has?

MattIPv4 avatar Oct 28 '25 13:10 MattIPv4

I didn't look too close at the downstream usages, but it has a lot of date logic:

    const support = {
      currentStart: major.support.phases.dates.start,
      ltsStart: major.support.phases.dates.lts,
      maintenanceStart: major.support.phases.dates.maintenance,
      endOfLife: major.support.phases.dates.end,
    };

    // Get the major release status based on our Release Schedule
    const status = getNodeReleaseStatus(new Date(), support);

    const minorVersions = Object.entries(major.releases).map(([, release]) => ({
      modules: release.modules.version || '',
      npm: release.dependencies.npm || '',
      releaseDate: release.releaseDate,
      v8: release.dependencies.v8,
      version: release.semver.raw,
      versionWithPrefix: `v${release.semver.raw}`,
    }));

bmuenzenmeyer avatar Oct 28 '25 14:10 bmuenzenmeyer

Yeh, all that date logic is what is causing us issues -- we can't just assume that because we've hit a given date that the release timeline has, that existing releases have suddenly changed status.

MattIPv4 avatar Oct 28 '25 14:10 MattIPv4

There should be no date logic except for support status, only the dist data should be used for LTS (which is an immutable status applied to version numbers)

ljharb avatar Oct 28 '25 16:10 ljharb

fair - for what it's worth, we use cutenode/nodevu@main/core - i dont recall the origin of that decision. I am we can get to the bottom of it

nodevu also uses the same data sources, but might be doing some extra logic that leads to innacurate results? cc @bnb

ovflowd avatar Oct 29 '25 12:10 ovflowd

to be clear - i am not blaming nodevu - more trying to say that this issue, repeating now a couple times, the other instances i mentioned, should have us rethink our approach as a balance of maintenance and control

bmuenzenmeyer avatar Oct 29 '25 13:10 bmuenzenmeyer

I'm not saying you are, jus trying to better understand what the bug is and what's the proper way of fixing it. I do believe Tierney would be more than happy to support us, and we can also make a fix upstream on nodevu if the issue is there (I'm a committer there)

ovflowd avatar Oct 29 '25 13:10 ovflowd

nodevu uses our official schedule, which does off of dates, not releases

avivkeller avatar Oct 29 '25 14:10 avivkeller

I haven't looked at whether this logic lies in our code or nodevu, but wherever we're determining support status, we need to be comparing the schedule date to the most recent release rather than to the current date.

MattIPv4 avatar Oct 29 '25 14:10 MattIPv4

I haven't looked at whether this logic lies in our code or nodevu, but wherever we're determining support status, we need to be comparing the schedule date to the most recent release rather than to the current date.

Don't you mean that we should compare the current date with the scheduled EOL date to determine support status? I don't see how the most recent release date is relevant for that.

aduh95 avatar Oct 29 '25 14:10 aduh95

Sorry, yes, EOL would still want to use current date for the comparison, but LTS (which is what I was referring to in the context of this issue) should check against release date rather than current date.

MattIPv4 avatar Oct 29 '25 14:10 MattIPv4

LTS (which is what I was referring to in the context of this issue) should check against release date rather than current date.

I disagree, index.json should be the source of truth, no matter what date it is, or the release date, and no matter what the schedule says. The first marked as LTS there should be the one "advertized" as latest LTS, as suggested in https://github.com/nodejs/nodejs.org/issues/8277#issuecomment-3456030496.

aduh95 avatar Oct 29 '25 14:10 aduh95

That is going to rather complicate things I suspect, needing to use the release schedule for some bits of support status but not others...

MattIPv4 avatar Oct 29 '25 14:10 MattIPv4

i started a branch to coalesce the two data sources together, but it still needs a lot of work and tests

bmuenzenmeyer avatar Oct 29 '25 14:10 bmuenzenmeyer

we need date to know the Current version

Here a small script that I had used to know what happened so not the website code

import { major } from '@vltpkg/semver';

const INDEX_LINK = "https://nodejs.org/download/release/index.json";
const SCHEDULE_LINK = "https://raw.githubusercontent.com/nodejs/Release/main/schedule.json";

const releasesData = await fetch(INDEX_LINK).then(res => res.json());
const scheduleData = await fetch(SCHEDULE_LINK).then(res => res.json());

const getLatestLTS = (releases) => releases.find(release => release.lts !== false);
const getLatestCurrent = (releases, schedule) => {
    const today = new Date();
    let currentMajor = null;
    let latestStartTime = -Infinity;

    for (const [key, info] of Object.entries(schedule)) {
        if (!info || !info.start) continue;
        const start = new Date(info.start);
        const end = info.end ? new Date(info.end) : null;
        if (isNaN(start)) continue;

        // Consider schedule entries that have started and not yet ended
        if (start <= today && (!end || today < end)) {
            const startTime = start.getTime();
            if (startTime > latestStartTime) {
                latestStartTime = startTime;
                currentMajor = parseInt(key.replace(/^v/, ''), 10);
            }
        }
    }

    // fallback: if we couldn't find a matching schedule entry, pick the most
    // recently published non-LTS release
    if (!currentMajor) {
        const nonLTS = releases.filter(r => r.lts === false);
        if (!nonLTS.length) return null;
        return nonLTS.sort((a, b) => new Date(b.date) - new Date(a.date))[0];
    }

    // find releases matching the major and return the most recently published one
    const candidates = releases.filter(r => major(r.version) === currentMajor);
    if (!candidates.length) return null;
    return candidates.sort((a, b) => new Date(b.date) - new Date(a.date))[0];
}
const getLTS = (releases) => 
    releases
    .filter(item => item.lts !== false)
    .reduce((acc, release) => {
        const majorVersion = major(release.version);
        if (!acc.find(item => major(item.version) === majorVersion)) {
            acc.push(release);
        }
        return acc;
    }, []);
const getCurrent = (releases, schedule) => {
    const today = new Date();

    const currentMajors = Object.entries(schedule)
        .filter(([key, info]) => info && info.start && !isNaN(new Date(info.start)))
        .filter(([key, info]) => {
            const start = new Date(info.start);
            const end = info.end ? new Date(info.end) : null;
            return start <= today && (!end || today < end);
        })
        .map(([key]) => parseInt(String(key).replace(/^v/, ''), 10))
        .filter(n => !isNaN(n));

    if (!currentMajors.length) return [];

    const majorsSet = new Set(currentMajors);

    return releases
        .filter(r => majorsSet.has(major(r.version)))
        .sort((a, b) => {
            const ma = major(a.version);
            const mb = major(b.version);
            if (ma !== mb) return mb - ma; // higher major first
            return new Date(b.date) - new Date(a.date); // newer first within same major
        })
        .reduce((acc, release) => {
            const majorVersion = major(release.version);
            if (!acc.find(item => major(item.version) === majorVersion)) {
                acc.push(release);
            }
            return acc;
        }, [])
        .filter(r => r.lts === false);
};

AugustinMauroy avatar Oct 29 '25 16:10 AugustinMauroy

we need date to know the Current version

No you don't, the latest current is always the very first item in index.json

aduh95 avatar Oct 29 '25 17:10 aduh95

we need date to know the Current version

No you don't, the latest current is always the very first item in index.json

that's an interesting API contract 😅

bmuenzenmeyer avatar Oct 29 '25 18:10 bmuenzenmeyer

I mean you can use semver sort if you don't want to depend on that, it's not a hard problem to find the latest version of something :-)

ljharb avatar Oct 30 '25 06:10 ljharb

That's how the index.json is generated: https://github.com/nodejs/nodejs-dist-indexer/blob/caaf5af34214355494d43f8f089d3f4b0afbad20/src/dist-indexer.js#L590

Anyway, my point is that comparing dates should only be used to report EOL status, otherwise it should be irrelevant.

aduh95 avatar Oct 30 '25 09:10 aduh95

Anyway, my point is that comparing dates should only be used to report EOL status, otherwise it should be irrelevant.

Do we revalidate index.json ? If yes we can just add new propriety for EOL on it.

AugustinMauroy avatar Oct 30 '25 10:10 AugustinMauroy

Totally makes sense that there’s some drift here with these two sources and index.json should be prioritized. Probably worth looking at automating updating Scheudle.json since I know I’m not the only one relying on it :)

That said, totally agree about index.json being prioritized. In theory, should be pretty easy to check the data and see if index.json is more up to date and use that instead once we have that data - this is the exact kind of API smoothness I built nodevu to provide.

I’m about to get on a ~9 hour flight home from SFO, should be able to get a PR up in that time. Will reference this issue from it, should be a seamless fix for the website once published.

bnb avatar Oct 30 '25 16:10 bnb

Hey @bnb friendly ping/bump on this ❤️

ovflowd avatar Dec 30 '25 21:12 ovflowd