Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Pulumi By James Joyner IV · · 9 min read

Pulumi Error: 'Running program failed with an unhandled exception' — Cause, Fix, and Troubleshooting Guide

Quick answer

Fix Pulumi 'Running program failed with an unhandled exception' when your Node.js/TypeScript Pulumi program throws an uncaught error. Causes and fixes.

  • #pulumi
  • #iac
  • #troubleshooting
  • #errors
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

error: Running program '/app' failed with an unhandled exception: means your Pulumi program itself threw an exception that nothing caught. The Node.js language host (pulumi-language-nodejs) runs your index.ts/index.js, and when that code throws — synchronously, in a promise, or inside an .apply() callback — the host reports it back to the engine as an unhandled exception and aborts the deployment.

This is a generic wrapper: the real cause is always printed on the lines below it, typically a JavaScript stack trace. The path shown ('/app', or your project directory) is just the program’s working directory. Your job is to read the underlying exception and fix the offending code or configuration.

error: Running program '/app' failed with an unhandled exception:
    TypeError: Cannot read properties of undefined (reading 'id')
        at /app/index.ts:42:31
        at processTicksAndRejections (node:internal/process/task_queues:95:5)

Symptoms

  • pulumi up or pulumi preview fails during the program-execution phase, before or during resource registration.
  • The banner line is followed by a Node.js/TypeScript stack trace pointing into your own source files.
  • Errors like TypeError, ReferenceError, SyntaxError, thrown Error("..."), or an unhandled promise rejection appear underneath.
  • pulumi preview fails the same way pulumi up does — the program runs in both.

Common Root Causes

1. A bug in your program code throws at runtime

The exception is genuinely from your code: dereferencing undefined, calling a function that doesn’t exist, or an assertion you wrote. Read the stack trace’s top frame — it names the file and line.

TypeError: config.subnets.map is not a function
    at /app/index.ts:58:24

2. Missing required configuration or environment values

Reading a required config value that isn’t set throws. Config.require() and requireSecret() throw by design when the key is absent.

const config = new pulumi.Config();
// Throws "Missing required configuration variable 'dbPassword'" if unset.
const dbPassword = config.requireSecret("dbPassword");

3. An unhandled promise rejection

async work that rejects without a .catch() bubbles up as an unhandled exception. A common shape is doing an await at the top level to an API that fails (bad credentials, missing resource).

// If getAmi rejects, nothing catches it → unhandled exception.
const ami = await aws.ec2.getAmi({ owners: ["amazon"], mostRecent: true,
  filters: [{ name: "name", values: ["al2023-ami-*-x86_64"] }] });

4. Throwing inside an .apply() callback

Code that runs inside output.apply(cb) executes later; if cb throws, it surfaces as an unhandled exception during deployment rather than at graph-construction time.

bucket.id.apply(id => {
  // Throws if id doesn't match the expected format.
  return id.split("-")[3].toUpperCase(); // undefined.toUpperCase() → TypeError
});

5. A syntax or module-resolution error at load time

A SyntaxError in TypeScript, or importing a module that isn’t installed, throws while the host is loading index.ts.

Error: Cannot find module 'aws-sdk'
Require stack: - /app/index.ts

How to Diagnose

Read the underlying trace first; then reproduce it outside Pulumi if needed.

# Re-run with verbose logging to capture the full stack trace and host output.
pulumi up --logtostderr -v=3 2> pulumi-debug.log

# Type-check the TypeScript program independently — catches many bugs early.
npx tsc --noEmit

# Confirm all imports resolve and deps are installed.
npm ls --all 2>/dev/null | grep -i "missing\|UNMET" || echo "deps look complete"
# List what config keys the stack actually has, to catch missing values.
pulumi config
pulumi config --show-secrets   # be careful where you run this
# For a suspected async/API failure, run the offending call in a REPL/script
# with the same credentials to see the raw provider error.
node -e "require('@pulumi/aws'); console.log('module loads OK')"

Fixes

Read the trace and fix the throwing line: The top non-framework frame (a path into your project) is the bug. Fix the dereference, typo, or bad assumption there. For a TypeError: Cannot read properties of undefined, guard the value or fix the ordering.

// Before: assumes vpc is always present
const subnetId = vpc.privateSubnets[0].id;

// After: validate, or make the dependency explicit
if (!vpc.privateSubnets?.length) {
  throw new Error("vpc.privateSubnets is empty — check the VPC component inputs");
}
const subnetId = vpc.privateSubnets[0].id;

Set the missing configuration: If the trace says a config variable is missing, set it (mark secrets as secret).

pulumi config set dbUser appuser
pulumi config set --secret dbPassword 'REDACTED'
pulumi up

Handle async rejections explicitly: Wrap top-level await work so failures produce a clear message, or move data lookups into pulumi.output(...) chains.

async function main() {
  const ami = await aws.ec2.getAmi({ owners: ["amazon"], mostRecent: true,
    filters: [{ name: "name", values: ["al2023-ami-*-x86_64"] }] });
  return { amiId: ami.id };
}

// Surface a readable error instead of a bare rejection.
export const outputs = main().catch(err => {
  throw new Error(`AMI lookup failed: ${err.message}`);
});

Fix module/syntax errors: Install the missing package or correct the import, then re-run.

npm install @pulumi/aws
npx tsc --noEmit   # confirm the program compiles
pulumi up

What to Watch Out For

  • The banner line is never the whole story — the actionable detail is always in the stack trace beneath it.
  • Errors inside .apply() callbacks appear at deployment time, not construction time; test that logic carefully.
  • Config.require() throws by design — prefer config.get() with a default when a value is genuinely optional.
  • An unhandled promise rejection can abort the whole run; always .catch() top-level async work.
  • Run npx tsc --noEmit in CI before pulumi up to catch a whole class of these before they ever reach the engine.
Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.