Node 24.x not yet marked as LTS ("Krypton") in index.json (website lists v24.10.0 as Latest LTS)
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?
- Visit https://nodejs.org/dist/index.json
- Search for any entries for version v24.x.x
→ all of them have
"lts": false. - Visit https://nodejs.org – it lists v24.10.0 as “Latest LTS”.
- 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 🙏
This is a bug with the website. The first v24 LTS will be v24.11.0 (to be released today)
Duplicate of https://github.com/nodejs/nodejs.org/issues/7153?
@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).
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.
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.
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
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.
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
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?
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}`,
}));
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.
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)
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
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
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)
nodevu uses our official schedule, which does off of dates, not releases
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.
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.
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.
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.
That is going to rather complicate things I suspect, needing to use the release schedule for some bits of support status but not others...
i started a branch to coalesce the two data sources together, but it still needs a lot of work and tests
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);
};
we need date to know the Current version
No you don't, the latest current is always the very first item in index.json
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 😅
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 :-)
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.
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.
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.
Hey @bnb friendly ping/bump on this ❤️