# Process Entrypoints (runMain)

When an Effect is the **root of a process** — a CLI command, a server, a worker —
you want more than a bare `runFork`. You want failures reported with a non-zero
exit code, `SIGINT`/`SIGTERM` (Ctrl+C, container stop) to interrupt the program
so finalizers run, and the process to exit cleanly once work is done. That is
exactly what `runMain` provides.

`runMain` is the *last* line of your `main.ts`: build the program, provide every
layer it needs, then hand the self-contained `Effect<A, E, never>` to your
platform's runner.

```ts
// main.ts
import { NodeRuntime } from "@effect/platform-node"
import { Effect, Layer } from "effect"

// A small worker that logs and then spawns a background loop. `forkScoped`
// ties the loop's lifetime to the surrounding scope, so it is interrupted
// automatically on shutdown.
const Worker = Layer.effectDiscard(
  Effect.gen(function*() {
    yield* Effect.logInfo("Starting worker...")
    yield* Effect.forkScoped(
      Effect.gen(function*() {
        while (true) {
          yield* Effect.logInfo("Working...")
          yield* Effect.sleep("1 second")
        }
      })
    )
  })
)

// `Layer.launch` turns the layer into a long-running Effect (see below).
const program = Layer.launch(Worker)

// Make it the process root. runMain installs SIGINT/SIGTERM handlers and
// interrupts running fibers for graceful shutdown.
NodeRuntime.runMain(program)
```

## What runMain does for you

Under the hood, `runMain` is `Effect.runFork` plus the boundary behaviour every
real program needs:

- **Error reporting** — an unreported, non-interruption failure cause is logged
  (via `Effect.logError`) and the process exits with a non-zero code.
  Interruptions are not treated as errors.
- **Signal handling** — `SIGINT` and `SIGTERM` interrupt the main fiber rather
  than killing the process abruptly, giving scoped finalizers a chance to run.
- **Keep-alive and teardown** — the process stays alive while the main fiber is
  running, then tears down (and sets the exit code) once it completes.

The default exit-code mapping (`Runtime.defaultTeardown`) is:

| Exit                         | Process exit code                 |
| ---------------------------- | --------------------------------- |
| Success                      | `0`                               |
| Interruption only            | `130`                             |
| Failure with `errorExitCode` | that code                         |
| Any other failure            | `1`                               |
**Keep resources in scopes:** Graceful shutdown only works if your long-lived resources live in Effect
  scopes. Acquire them with `Effect.acquireRelease` or as
  [layers](https://effect.plants.sh/services-and-layers/) so their finalizers run when the main fiber is
  interrupted. Avoid finalizers that can hang forever.

## Node and Bun

The API is identical across platforms — only the NodeRuntime.runMain(program)
```

```ts
// Bun
BunRuntime.runMain(program)
```

`runMain` does **not** provide platform services itself. If your program uses
file system, HTTP server, or other platform APIs, provide the corresponding
layers (for example `NodeServices.layer` / `BunServices.layer`) before
launching.
**Clean exits drain the event loop:** On a clean success the Node/Bun runner lets the event loop drain naturally
  rather than forcing `process.exit(0)`. It only calls `process.exit` when a
  signal was received or when teardown returns a non-zero code. That means a
  dangling timer or open handle can keep the process alive after the main fiber
  succeeds — another reason to scope long-lived resources.

## Options

`runMain` accepts an optional configuration object:

```ts
const program = Effect.logInfo("hello")

NodeRuntime.runMain(program, {
  // Disable the built-in failure logging if your app already centralizes
  // error reporting (e.g. through your own observability layer).
  disableErrorReporting: true,
  // Provide custom finalization / exit-code logic at the process boundary.
  teardown: (exit, onExit) => onExit(0)
})
```

- `disableErrorReporting` — turn off the automatic `Effect.logError` emitted for
  unreported failures. It does **not** change the `Exit`, interruption
  behaviour, or the teardown exit-code rules.
- `teardown` — supply your own [`Runtime.Teardown`](#runtimeteardown) to control
  finalization and the process exit code; the default reports a non-zero code on
  failure.

You can also tag individual errors to control reporting and exit codes via the
[`Runtime.errorReported`](#runtimeerrorreported) and
[`Runtime.errorExitCode`](#runtimeerrorexitcode) markers, which lets specific
error types opt out of logging or set a custom code.

## When to use it

Use `runMain` whenever an Effect is your process entrypoint — it is the right
default for almost every standalone application. Reach for the lower-level
[`Effect.run*` functions](https://effect.plants.sh/runtime/running-effects/) only when you are embedding
Effect inside an existing host that already owns the process lifecycle (a web
framework handler, a test, an existing async function). For apps composed
entirely of layers, pair `runMain` with
[`Layer.launch`](https://effect.plants.sh/runtime/layer-launch/).

---

## Runtime module internals

The platform runners are thin wrappers around the core `Runtime` module
(imported from `"effect"`). `NodeRuntime.runMain` and `BunRuntime.runMain` are
each just one call to `Runtime.makeRunMain` that wires up `process` signal
handlers and `process.exit`. Most applications never touch this layer — reach
for it only when you are building a custom host (a new platform adapter, a test
harness, a worker pool, an embedded runtime).
**You usually do not need this:** If you are writing an application, call `NodeRuntime.runMain` /
  `BunRuntime.runMain`. The APIs below are for authoring *new* entrypoints.

### `Runtime.makeRunMain`

Builds a platform `runMain` from a setup callback. Your callback receives the
already-forked `fiber` and a `teardown` function; it is responsible for
observing the fiber and eventually calling `teardown`. This is exactly how
`NodeRuntime.runMain` is implemented (it adds `SIGINT`/`SIGTERM` handlers and
calls `process.exit` from the teardown's `onExit`).

```ts
const runMain = Runtime.makeRunMain(({ fiber, teardown }) => {
  fiber.addObserver((exit) => {
    teardown(exit, (code) => {
      console.log(`finished with exit code ${code}`)
      // a real host would call process.exit(code) here
    })
  })
})

runMain(Effect.log("booted"))
// => logs "booted", then "finished with exit code 0"
```

`makeRunMain` forks the effect for you. When `disableErrorReporting` is not set,
it wraps the effect in `Effect.tapCause` so non-interruption failures whose
squashed error is "reported" get logged via `Effect.logError`. It also installs
a long keep-alive interval (cleared when the fiber completes) so the host process
does not exit before the main fiber finishes.

The returned runner is `dual`, so it accepts both data-first and data-last forms:

```ts
const runMain = Runtime.makeRunMain(({ fiber, teardown }) => {
  fiber.addObserver((exit) => teardown(exit, () => {}))
})

// data-first: pass the effect directly
runMain(Effect.log("a"))

// data-last: pass options first, then apply to the effect (pipeable)
Effect.log("b").pipe(runMain({ disableErrorReporting: true }))
```

### `Runtime.Teardown`

The type of a teardown function: `(exit, onExit) => void`. Given the program's
`Exit`, it computes a process exit code and hands it to `onExit`. Supply a custom
one via the `teardown` option to override exit-code behaviour.

```ts
const customTeardown: Runtime.Teardown = (exit, onExit) => {
  if (Exit.isSuccess(exit)) {
    onExit(0)
  } else {
    // exit code 2 for any failure instead of the default 1
    onExit(2)
  }
}

// Use with any runMain:
// NodeRuntime.runMain(program, { teardown: customTeardown })
```

### `Runtime.defaultTeardown`

The standard `Teardown` used when you do not pass one. It maps success to `0`,
interruption-only causes to `130`, and other failures to the squashed error's
[`errorExitCode`](#runtimeerrorexitcode) (falling back to `1`). Call it directly
when your custom teardown only needs to add behaviour around the default rules.

```ts
const logExitCode = (exit: Exit.Exit<any, any>) =>
  Runtime.defaultTeardown(exit, (code) => console.log(`Exit code: ${code}`))

logExitCode(Exit.succeed(42))
// => Exit code: 0

logExitCode(Exit.fail("error"))
// => Exit code: 1

logExitCode(Exit.interrupt(123))
// => Exit code: 130
```

```ts
// A teardown that runs extra logging, then defers to the default rules.
const teardown: Runtime.Teardown = (exit, onExit) => {
  console.log("shutting down...")
  Runtime.defaultTeardown(exit, onExit)
}
```

### `Runtime.errorExitCode`

A marker key you attach to an error to set the process exit code when that error
reaches the default teardown. `defaultTeardown` reads it from the *squashed*
cause, so it works through wrapped failures.

```ts
class ConfigError extends Data.TaggedError("ConfigError") {
  readonly [Runtime.errorExitCode] = 78 // EX_CONFIG (sysexits.h)
}

// If the program fails with ConfigError, the process exits with code 78.
NodeRuntime.runMain(Effect.fail(new ConfigError()))
// => logs the error, process.exit(78)
```

`Runtime.errorExitCode` is also exported as a `type` (the string literal
`"~effect/Runtime/errorExitCode"`) for typing the marker property on custom
error classes.

### `Runtime.getErrorExitCode`

Reads the `errorExitCode` marker from an unknown value, returning `1` when it is
absent or not a number. This is the function `defaultTeardown` uses to compute
the failure exit code.

```ts
Runtime.getErrorExitCode({ [Runtime.errorExitCode]: 42 })
// => 42

Runtime.getErrorExitCode(new Error("boom"))
// => 1

Runtime.getErrorExitCode("not an object")
// => 1
```

### `Runtime.errorReported`

A marker key you attach to an error to control whether `runMain` logs it. Set it
to `false` on errors that your application code has already reported, to avoid a
duplicate log. Omitted or non-boolean values are treated as `true` (logged).

```ts
class AlreadyLogged extends Data.TaggedError("AlreadyLogged") {
  readonly [Runtime.errorReported] = false
}

// Process exits with code 1, but runMain emits no log for this failure.
NodeRuntime.runMain(Effect.fail(new AlreadyLogged()))
// => process.exit(1), no error log
```

Like `errorExitCode`, `Runtime.errorReported` is also exported as a `type` (the
literal `"~effect/Runtime/errorReported"`) for typing the marker property. The
marker only affects automatic logging — it never changes the `Exit` or the exit
code.

### `Runtime.getErrorReported`

Reads the `errorReported` marker from an unknown value, returning `true` when it
is absent or not a boolean. This is what `makeRunMain` consults (on the squashed
cause) before logging a failure.

```ts
Runtime.getErrorReported({ [Runtime.errorReported]: false })
// => false

Runtime.getErrorReported(new Error("boom"))
// => true (no marker, so logged by default)

Runtime.getErrorReported(null)
// => true
```
**Markers are read from the squashed cause:** Both markers are read from `Cause.squash(cause)`. When a failure cause
  contains multiple failures, the squashed failure value is what determines the
  exit code and whether the error is logged.