Checkboxes for prior research
Describe the bug
Response deserialization throws whenever an awsJson response body contains a JSON number written in exponent notation (e.g. 1.784316439424E9). Exponent notation is valid JSON per RFC 8259, and AWS services do emit it — ECS serializes epoch-second timestamps that way, so every ecs:DescribeServices call now fails.
Error: @smithy/core/serde - NumericValue must only contain [0-9], at most one decimal point ".", and an optional negation prefix "-".
Deserialization error: to see the raw response, inspect the hidden field {error}.$response on this object.
The same responses parse correctly with botocore / the AWS CLI, and parsed correctly with the JS SDK before this regression.
Regression Issue
Last good: @aws-sdk/core@3.976.0
First bad: @aws-sdk/core@3.977.0 (published 2026-07-24, shipped with v3.1095.0)
Suspect: #8214 (chore(core/protocols): JSON serde consistency testing and perf tuning), which touches parseJsonBody.ts, needsReviver.ts, and JsonShapeDeserializer.ts.
Note this floats independently of the service client — pinning @aws-sdk/client-* or @smithy/core does not avoid it, because @aws-sdk/core is resolved by range.
SDK version number
@aws-sdk/core@3.977.0, reproduced with @aws-sdk/client-ecs@3.1094.0 and 3.1095.0 (any client using the awsJson protocol should reproduce)
Which JavaScript Runtime is this issue in?
Node.js
Details of the browser/Node.js/ReactNative version
Node.js v24.12.0 (Linux and macOS). Requires a runtime with JSON.parse source access (Node ≥ 22) — on older Node the reviver's context is undefined and the bug does not trigger.
Reproduction Steps
Fully self-contained: no AWS account or credentials needed, since the HTTP layer is stubbed with a canned response body.
mkdir repro && cd repro
npm init -y >/dev/null
npm pkg set type=module
npm install @aws-sdk/client-ecs@3.1094.0 @smithy/protocol-http
repro.mjs:
import { ECS } from "@aws-sdk/client-ecs";
import { HttpResponse } from "@smithy/protocol-http";
import { Readable } from "node:stream";
// A minimal awsJson response. `createdAt` uses exponent notation, exactly as
// real AWS services serialize epoch-second timestamps.
const BODY =
'{"failures":[],"services":[{"serviceName":"my-service","status":"ACTIVE","createdAt":1.784316439424E9}]}';
const client = new ECS({
region: "us-east-1",
credentials: { accessKeyId: "AKIAIOSFODNN7EXAMPLE", secretAccessKey: "secret" },
requestHandler: {
async handle() {
return {
response: new HttpResponse({
statusCode: 200,
headers: { "content-type": "application/x-amz-json-1.1" },
body: Readable.from([BODY]),
}),
};
},
},
});
try {
const out = await client.describeServices({ cluster: "my-cluster", services: ["my-service"] });
console.log("OK ->", JSON.stringify(out.services));
} catch (e) {
console.log("FAIL ->", e.message.split("\n")[0]);
}
node repro.mjs
# FAIL -> @smithy/core/serde - NumericValue must only contain [0-9], at most one decimal point ".", ...
# Now pin the previous version of @aws-sdk/core and repeat:
npm pkg set overrides.@aws-sdk/core=3.976.0 && npm install
node repro.mjs
# OK -> [{"serviceName":"my-service","status":"ACTIVE","createdAt":"2026-07-17T19:27:19.424Z"}]
Against the live API, ecs:DescribeServices reproduces it for any service, because ECS returns createdAt / updatedAt in exponent form.
Observed Behavior
describeServices (and any awsJson call whose response contains an exponent-notation number) rejects with the NumericValue error above. Nothing is returned to the caller, and the error text names neither the field nor the offending value.
Expected Behavior
The response deserializes, with createdAt becoming a Date (as it does on @aws-sdk/core@3.976.0). Exponent notation is valid JSON and should be accepted wherever a plain decimal is.
Possible Solution
The reviver in @aws-sdk/core (submodules/protocols/index.js, jsonReviver) decides a token is precision-losing by comparing the raw source text against String(value):
function jsonReviver(key, value, context) {
if (context?.source) {
const numericString = context.source;
if (typeof value === "number") {
if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER || numericString !== String(value)) {
const isFractional = numericString.includes(".");
if (isFractional) {
return new NumericValue(numericString, "bigDecimal"); // throws on "1.78E9"
} else {
return BigInt(numericString); // throws on "1E9"
}
}
}
}
return value;
}
An exponent-notation token always fails numericString !== String(value) ("1.784316439424E9" !== "1784316439.424"), so it is misclassified as precision-losing even though the double round-trips exactly. Both branches then reject it:
- fractional →
NumericValue, whose format is /^-?\d*(\.\d+)?$/ and forbids E
- non-fractional →
BigInt("1E9") throws SyntaxError: Cannot convert 1E9 to a BigInt
Suggested fixes, roughly in order of smallest blast radius:
- Normalize the exponent away before the comparison / before constructing
NumericValue, so exponent form is expanded to plain decimal.
- Treat a token as precision-losing only when it genuinely does not round-trip — compare numerically rather than by string equality (e.g. against
Number(numericString)), which lets 1.78E9 fall through to the plain number path.
- Widen the
NumericValue format in @smithy/core to accept the full JSON number grammar, including exponents, and make the non-fractional branch use a parser that accepts them.
Whichever direction, a regression test with exponent-notation input on both branches (1.5E9 and 1E9) would pin it.
Additional Information/Context
Impact is broad and not opt-in: any code path that reads an ECS service description with the JS SDK fails, which includes common CI/CD deployment tooling built on the SDK. The failure surfaces as an opaque error with no field name, and — because tools often capture the SDK's stdout — can present as an exit code with no message at all, which makes it expensive to diagnose.
botocore and the AWS CLI parse the identical response bodies without complaint, so this is specific to the JS SDK's reviver.
Checkboxes for prior research
Describe the bug
Response deserialization throws whenever an
awsJsonresponse body contains a JSON number written in exponent notation (e.g.1.784316439424E9). Exponent notation is valid JSON per RFC 8259, and AWS services do emit it — ECS serializes epoch-second timestamps that way, so everyecs:DescribeServicescall now fails.The same responses parse correctly with botocore / the AWS CLI, and parsed correctly with the JS SDK before this regression.
Regression Issue
Last good:
@aws-sdk/core@3.976.0First bad:
@aws-sdk/core@3.977.0(published 2026-07-24, shipped withv3.1095.0)Suspect: #8214 (
chore(core/protocols): JSON serde consistency testing and perf tuning), which touchesparseJsonBody.ts,needsReviver.ts, andJsonShapeDeserializer.ts.Note this floats independently of the service client — pinning
@aws-sdk/client-*or@smithy/coredoes not avoid it, because@aws-sdk/coreis resolved by range.SDK version number
@aws-sdk/core@3.977.0, reproduced with@aws-sdk/client-ecs@3.1094.0and3.1095.0(any client using the awsJson protocol should reproduce)Which JavaScript Runtime is this issue in?
Node.js
Details of the browser/Node.js/ReactNative version
Node.js v24.12.0 (Linux and macOS). Requires a runtime with
JSON.parsesource access (Node ≥ 22) — on older Node the reviver'scontextis undefined and the bug does not trigger.Reproduction Steps
Fully self-contained: no AWS account or credentials needed, since the HTTP layer is stubbed with a canned response body.
repro.mjs:Against the live API,
ecs:DescribeServicesreproduces it for any service, because ECS returnscreatedAt/updatedAtin exponent form.Observed Behavior
describeServices(and any awsJson call whose response contains an exponent-notation number) rejects with theNumericValueerror above. Nothing is returned to the caller, and the error text names neither the field nor the offending value.Expected Behavior
The response deserializes, with
createdAtbecoming aDate(as it does on@aws-sdk/core@3.976.0). Exponent notation is valid JSON and should be accepted wherever a plain decimal is.Possible Solution
The reviver in
@aws-sdk/core(submodules/protocols/index.js,jsonReviver) decides a token is precision-losing by comparing the raw source text againstString(value):An exponent-notation token always fails
numericString !== String(value)("1.784316439424E9" !== "1784316439.424"), so it is misclassified as precision-losing even though thedoubleround-trips exactly. Both branches then reject it:NumericValue, whose format is/^-?\d*(\.\d+)?$/and forbidsEBigInt("1E9")throwsSyntaxError: Cannot convert 1E9 to a BigIntSuggested fixes, roughly in order of smallest blast radius:
NumericValue, so exponent form is expanded to plain decimal.Number(numericString)), which lets1.78E9fall through to the plainnumberpath.NumericValueformat in@smithy/coreto accept the full JSON number grammar, including exponents, and make the non-fractional branch use a parser that accepts them.Whichever direction, a regression test with exponent-notation input on both branches (
1.5E9and1E9) would pin it.Additional Information/Context
Impact is broad and not opt-in: any code path that reads an ECS service description with the JS SDK fails, which includes common CI/CD deployment tooling built on the SDK. The failure surfaces as an opaque error with no field name, and — because tools often capture the SDK's stdout — can present as an exit code with no message at all, which makes it expensive to diagnose.
botocoreand the AWS CLI parse the identical response bodies without complaint, so this is specific to the JS SDK's reviver.Hello, thanks for reporting this issue. I released a patch that should be available with the following command:
Specifically https://www.npmjs.com/package/@aws-sdk/core/v/3.977.1.
This command updates the presumed package lockfile without taking a direct dependency like
npm installwould.I also modified the regex validator for NumericValue in smithy-typescript, so both packages can be updated:
(https://www.npmjs.com/package/@smithy/core/v/3.30.0)
I tested ECS DescribeServices and it worked, but will continue to scan for additional test cases.