Skip to content

util.inspect throws when given a ZodError #60717

Description

@mnahkies

Version

v24.11.1

Platform

Darwin <hostname> 24.6.0 Darwin Kernel Version 24.6.0: Mon Aug 11 21:16:34 PDT 2025; root:xnu-11417.140.69.701.11~1/RELEASE_ARM64_T6020 arm64

Subsystem

util

What steps will reproduce the bug?

Using zod@3.25.76

const { inspect } = require('node:util');
const { z } = require('zod');

inspect(z.object({ foo: z.string() }).safeParse().error);

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

Seems to happen on any ZodError

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

It should inspect the error, same as it did on Node v22

What do you see instead?

Exception.

node:internal/util/inspect:1183
    if (!isStackOverflowError(err)) throw err;
                                    ^

TypeError: Cannot read properties of undefined (reading 'value')
    at formatProperty (node:internal/util/inspect:2279:12)
    at formatRaw (node:internal/util/inspect:1176:9)
    at formatValue (node:internal/util/inspect:932:10)
    at inspect (node:internal/util/inspect:409:10)
    at Object.<anonymous> (/junk.js:4:1)
    at Module._compile (node:internal/modules/cjs/loader:1761:14)
    at Object..js (node:internal/modules/cjs/loader:1893:10)
    at Module.load (node:internal/modules/cjs/loader:1481:32)
    at Module._load (node:internal/modules/cjs/loader:1300:12)
    at TracingChannel.traceSync (node:diagnostics_channel:328:14)

Node.js v24.11.1

Additional information

I believe this is a regression caused by 748d4f6 - based on the timing / line numbers.

Activity

  1. siaeyy commented on Nov 15, 2025

    @siaeyy
    Contributor

    Given code to reproduce works fine in version v26.0.0-pre

  2. BridgeAR commented on Nov 15, 2025

    @BridgeAR
    Member

    @siaeyy I can reproduce it with the mentioned zod version.

  3. siaeyy commented on Nov 15, 2025

    @siaeyy
    Contributor

    @siaeyy I can reproduce it with the mentioned zod version.

    Sorry, I was using the latest version of zod

  4. added
    confirmed-bugIssues and PRs for confirmed bugs.
    utilIssues and PRs related to the built-in util module.
    on Nov 15, 2025
  5. BridgeAR commented on Nov 15, 2025

    @BridgeAR
    Member

    So the root cause is that the zod error has an errors property that is not an own property.

    With the change there are two properties were this could happen: cause and errors on errors. All other properties behave regularly and would not need a different handling. It is not an issue for most errors, since they would have these as own non-enumerable properties.

    The best fix I could think of right now is adding this right after the desc ??= ObjectGetOwnPropertyDescriptor(value, key) in the formatProperty() method:

      while (desc === undefined) {
        value = ObjectGetPrototypeOf(value);
        desc = ObjectGetOwnPropertyDescriptor(value, key);
      }

    That way it will have the correct enumerability and show that it is a getter. That would otherwise not be possible.

    I am just not happy that we seemingly have to add a special handling there just for these two properties.

    @mnahkies @siaeyy would one of you be willing to open a PR with that change and adding a regression test for both mentioned properties?

  6. siaeyy commented on Nov 15, 2025

    @siaeyy
    Contributor

    Of course I am willing to open a PR :)

  7. BridgeAR commented on Nov 15, 2025

    @BridgeAR
    Member

    It will actually be possible to handle it in a different spot as well by using the newly introduced extraKeys. The handler for those could than loop through the prototypes as described. That way the loop is only used in case extra keys are needed, which is a rarer case. This will however not have a real impact on the performance, due to the insignificance of the check compared to the rest of the algorithm. It just feels cleaner, due to also not having to manipulate the keys in those places anymore. The downside is that the main method will become even bigger than before.

  8. siaeyy commented on Nov 15, 2025

    @siaeyy
    Contributor

    I think here is a example of different spot:

    if ('cause' in err &&
    (keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
    ArrayPrototypePush(keys, 'cause');
    }
    // Print errors aggregated into AggregateError
    try {
    const errors = err.errors;
    if (ArrayIsArray(errors) &&
    (keys.length === 0 || !ArrayPrototypeIncludes(keys, 'errors'))) {
    ArrayPrototypePush(keys, 'errors');
    }
    } catch {
    // If errors is a getter that throws, we ignore the error.
    }

  9. BridgeAR commented on Nov 15, 2025

    @BridgeAR
    Member

    @siaeyy I am not sure I follow. That code is the root cause of the problem, correct. It adds keys that have to be handled, while not knowing who the owning object is. That is why I named these two properties above :)

  10. jaenw commented on Dec 19, 2025

    @jaenw

    This just broke our tests after a Node upgrade. ("fortunately", otherwise this would have badly failed in production)

    console logging methods failing in this manner is... very bad, as they should never throw an exception – these are quite often used in places eg. error handlers themselves, in which throwing another exception can cause a catastrophic failure (with possible permanent state corruption).

    I'd suggest looking into adding a general error boundary for console methods, as that's the software engineering best practice for situations like these.

  11. ollipa commented on Jan 14, 2026

    @ollipa

    @aduh95, it seems that the fix hasn't been included in v24 yet. Will the fix get included in v24 soon? This bug prevents people from updating to the latest version, which is particularly important now that multiple vulnerabilities were fixed in the latest release yesterday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-bugIssues and PRs for confirmed bugs.utilIssues and PRs related to the built-in util module.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions