Skip to content

eslint-factory: no-unsafe-promise-catch-error-property false-positives on err && err.stack ? err.stack : String(err) defensive [Content truncated due to length] #42913

Description

@github-actions

Summary

no-unsafe-promise-catch-error-property produces false positives on a common defensive error-formatting idiom that short-circuits on truthiness and falls back to String(err). This is a live false positive, not hypothetical.

Live example

actions/setup/js/apply_samples.cjs:650-652:

main().catch(err => {
  core.setFailed(err && err.stack ? err.stack : String(err));
});

The rule collects err.stack (it appears twice) and, because it recognizes only instanceof Error, getErrorMessage(err), and typeof err === 'object' && err !== null as guards, reports two unsafeProperty diagnostics here.

Why it's a false positive

The access is already safe against non-Error rejections:

  • err && err.stack proves err is truthy and has a truthy stack before the value is used.
  • Any non-Error / falsy-stack rejection (string, number, null, plain object without stack) short-circuits to String(err).

In every case the expression neither throws nor mis-formats, so the rule's premise — "the rejection value may not be an Error instance" — does not apply. Flagging it pushes contributors toward a churny rewrite of correct code (or scatters eslint-disable comments).

Suggested refinement

Treat a property access as guarded when it is truthiness-gated by the same variable/member, e.g.:

  • LogicalExpression err && err.<prop> (the && left operand proves err truthy), and/or
  • a ConditionalExpression whose test proves err/err.<prop> truthy and whose alternate is a String(err) coercion.

At minimum, recognizing the x && x.<prop> short-circuit (mirroring the existing non-null-guard logic in isNonNullGuardCheck) would clear this class of FP. The sibling rule no-unsafe-catch-error-property shares the same guard model and would benefit from the same recognition.

Acceptance criteria

  • promise.catch(err => core.setFailed(err && err.stack ? err.stack : String(err))) produces no diagnostic.
  • promise.catch(err => err && err.message ? err.message : 'unknown') produces no diagnostic.
  • Unguarded access (promise.catch(err => log(err.stack))) is still flagged (no regression).
  • Added valid + invalid test cases in no-unsafe-promise-catch-error-property.test.ts covering the truthiness-gated and String-fallback forms.

Filed by ESLint Refiner. Verified against eslint-factory/src/rules/no-unsafe-promise-catch-error-property.ts and actions/setup/js/apply_samples.cjs at time of writing.

Generated by 🤖 ESLint Refiner · 253.6 AIC · ⌖ 10.2 AIC · ⊞ 4.7K ·

  • expires on Jul 8, 2026, 10:35 PM UTC-08:00

Metadata

Metadata

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions