> For the complete documentation index, see [llms.txt](https://govplane.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://govplane.gitbook.io/docs/documentation/sdk-for-javascript-node.js/errors.md).

# Errors

Every SDK error carries a stable `code`. Messages are written for a human reading a log at three in the morning; **codes are what application code and alerting should match on**, and they do not change when the wording improves.

```ts
import { GovplaneError, BundleValidationError } from '@govplane/runtime-sdk';

try {
  await createLocalClient({ bundlePath, verify });
} catch (error) {
  if (error instanceof GovplaneError) {
    logger.error({ code: error.code, details: error.details }, error.message);
  }
  throw error;
}
```

### The class hierarchy

```
Error
└── GovplaneError            code, details
    ├── HttpError            + status, headers
    ├── BundleValidationError
    │   └── SafeBundleValidationError   (deprecated 1.x alias)
    ├── BundleSignatureError
    ├── RuleNotFoundError
    └── ConfigurationError
```

`instanceof` is reliable because the package ships a single ESM build that both `import` and `require` load — there is no second copy of these classes. See Installation.

### Codes

| Code                                     | Class                   | Meaning                                                          |
| ---------------------------------------- | ----------------------- | ---------------------------------------------------------------- |
| `BUNDLE_READ_ERROR`                      | `BundleValidationError` | The file is missing or unreadable                                |
| `BUNDLE_PARSE_ERROR`                     | `BundleValidationError` | The file is not valid JSON                                       |
| `BUNDLE_INVALID`                         | `BundleValidationError` | Structural validation failed; `details` names the specific issue |
| `CHECKSUM_MISMATCH`                      | `BundleValidationError` | Contents do not match the embedded checksum                      |
| `BUNDLE_UNSIGNED`                        | `BundleSignatureError`  | Unsigned, and `allowUnsigned` was not set                        |
| `BUNDLE_SIGNATURE_MISSING`               | `BundleSignatureError`  | A key is configured; the bundle has no signature                 |
| `BUNDLE_SIGNATURE_UNVERIFIED`            | `BundleSignatureError`  | Signed, but no key was configured to check it                    |
| `BUNDLE_SIGNATURE_INVALID`               | `BundleSignatureError`  | The signature does not match                                     |
| `BUNDLE_SIGNATURE_ALGORITHM_MISMATCH`    | `BundleSignatureError`  | Key algorithm ≠ signature algorithm                              |
| `BUNDLE_SIGNATURE_ALGORITHM_UNSUPPORTED` | `BundleSignatureError`  | Not acceptable from this source — HMAC from Cloud                |
| `BUNDLE_SIGNATURE_ALGORITHM_REMOVED`     | `BundleSignatureError`  | Ed25519                                                          |
| `BUNDLE_SIGNATURE_CONFIG_ERROR`          | `BundleSignatureError`  | Malformed key material                                           |
| `INVALID_CONFIGURATION`                  | `ConfigurationError`    | The client cannot be built as configured                         |
| `INVALID_EVALUATE_OPTIONS`               | `ConfigurationError`    | More than one of `asBoolean`, `asBinary`, `effectOnly`           |
| `RULE_NOT_FOUND`                         | `RuleNotFoundError`     | A rule or policy named in `evaluate` is not in the bundle        |
| `HTTP_ERROR`                             | `HttpError`             | A remote fetch failed; read `error.status`                       |

Bundle validation codes match `govplane validate`, so a code in your application log means the same thing as a code in the CLI's output. See Policy Bundle Schema.

### Where errors surface

This is the part worth internalising — the same underlying problem reaches you differently depending on when it happens.

| When                                                           | How it reaches you                                   |
| -------------------------------------------------------------- | ---------------------------------------------------- |
| **Construction** — first load, verification, bad config        | The factory promise **rejects**                      |
| **Background reload** — watch or poll                          | `onError(error)`; the previous bundle stays in force |
| **Evaluation** — bad options, unknown rule, context validation | **Throws** at the call site                          |

```ts
// 1. Construction — fail fast, at boot
const govplane = await createLocalClient({ … });   // throws if the bundle is bad

// 2. Background — never fails the request path
onError: (error) => logger.error({ error }, 'reload failed — serving previous bundle'),

// 3. Evaluation — only for caller mistakes
govplane.evaluate({ ruleId: 'typo' });             // RuleNotFoundError
```

{% hint style="info" %}
**Policy outcomes never throw.** A denial is a return value, not an exception. The only things that throw at evaluation time are caller mistakes: naming a rule that does not exist, setting two return-shape options, or passing context that violates a policy you configured.
{% endhint %}

#### Context validation errors are plain `Error`

```ts
govplane.evaluate({ target, context: { user: { role: 'admin' } } });
// Error: Invalid context type for user
```

These are thrown as plain `Error` with a descriptive message and **no `code`** — they are not `GovplaneError`. Treat them as programming errors: catch them in development, fix the call site, and do not build alerting on them.

Messages you may see:

| Message                                            | Cause                                                        |
| -------------------------------------------------- | ------------------------------------------------------------ |
| `Context key not allowed: <key>`                   | Not in `allowedKeys`                                         |
| `Context key looks like PII and is blocked: <key>` | Matched the PII heuristic — which overrides `allowedKeys`    |
| `Invalid context type for <key>`                   | A nested object, or a value that is not scalar or `string[]` |
| `Context value too long: <key>`                    | Exceeds `maxStringLen`                                       |
| `Context array too long: <key>`                    | Exceeds `maxArrayLen`                                        |

{% hint style="warning" %}
If you see `Context key not allowed` and you never configured a `contextPolicy`, you are almost certainly using `createPolicyEngine` directly. **The engine validates context by default; the clients do not** — it enforces a seven-key sample list (`plan`, `country`, `requestTier`, `feature`, `amount`, `isAuthenticated`, `role`). Pass `validateContext: false` or pin your own policy. See Using the engine directly.
{% endhint %}

See Context validation.

### Common failures

#### `No verification key was configured`

```
ConfigurationError [INVALID_CONFIGURATION]: No verification key was configured.
Pass verify with a key, or allowUnsigned: true to evaluate a bundle whose provenance is not checked.
```

You must make the choice explicitly. In development, `allowUnsigned: true`. In production, sign the bundle:

```bash
govplane build --signed
```

#### `BUNDLE_SIGNATURE_INVALID` after a bundle change

The bundle was modified after signing. Signatures cover a canonical projection — `schemaVersion`, `orgId`, `projectId`, `env` and the policies — so re-versioning or reformatting is safe, but editing a rule is not. Re-sign it.

If you are upgrading from 1.x and a signature that *used* to verify now fails, the 1.x verifier was checking the wrong bytes. See Migrating from 1.x.

#### `BUNDLE_SIGNATURE_ALGORITHM_REMOVED`

Ed25519. Re-sign with ECDSA:

```bash
govplane sign --signing-algorithm ECDSA_SHA_256
```

#### `BUNDLE_SIGNATURE_ALGORITHM_UNSUPPORTED` on a cloud bundle

HMAC from Govplane Cloud. Verifying HMAC means holding the signing secret, which a cloud signing key never exposes. Configure an ECDSA public key.

#### `ERR_PACKAGE_PATH_NOT_EXPORTED` or `ERR_REQUIRE_ESM`

Your Node.js is older than 20.19. Upgrade, or load the SDK through a dynamic `import()`. See ESM and CommonJS.

#### Everything is denied

Check `reason` and `policyKey` before assuming a bug:

```ts
{ decision: 'deny', reason: 'default' }   // ← no policyKey
```

No `policyKey` means **nothing in the bundle covered your target** — usually a target-string mismatch, since matching is exact and case-sensitive. Confirm with a trace:

```ts
const result = govplane.evaluateWithTrace({ target, context }, { level: 'full', force: true });
console.log(formatTrace(result.trace, { multiline: true, includeDiscarded: true }));
```

`reason=target_mismatch` on every rule confirms it. `reason=when_false` means the target was right and the condition was not — check what you passed in `context`.

### Retries

Do not retry `evaluate()`. It is deterministic: the same bundle and the same context produce the same decision, so a retry produces the same answer.

Remote fetch failures are already retried by the SDK with exponential backoff and jitter, and reported through `onError`. There is nothing for the application to retry.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://govplane.gitbook.io/docs/documentation/sdk-for-javascript-node.js/errors.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
