# How to Debug Cordis: Fiber-Based Debugging and Logging Strategies

> Debug Cordis effectively using fiber-based strategies. Enable detailed logging, inspect fiber state, and trace plugin lifecycles for efficient troubleshooting.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: how-to-guide
- Published: 2026-09-13

---

**Enable debug logging via `ctx.intercept('logger', { level: 3 })`, inspect fiber state at `ctx.fiber`, and wrap effects with labeled disposables to trace plugin lifecycles.**

Cordis is a fiber-based plugin framework that isolates contexts and tracks effects through a sophisticated lifecycle system. Learning how to debug Cordis effectively requires understanding its core architecture—specifically how the **Context**, **Fiber**, and **LoggerService** interact to manage plugin states and errors. This guide provides practical strategies for tracing issues within the `cordiverse/cordis` codebase using actual source implementations from the core and loader packages.

## Understanding the Cordis Debugging Architecture

Before setting breakpoints, you need to understand four key components that handle state isolation, lifecycle management, and message routing.

### Context and Service Isolation

The **Context** object in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts) (lines 9‑78) serves as the central hub for services including `events`, `logger`, `reflect`, and `registry`. It creates a proxy via `ReflectService.handler` that intercepts property access, enabling shadow isolation between plugins. Two critical methods for debugging are:

- **`isolate(name, label?)`** – Creates a new isolated context where a symbol is attached to `symbols.isolate` (lines 65‑69). This allows you to sandbox specific service instances.
- **`intercept(name, config)`** – Clones the intercept map to override service configurations on a per-plugin basis (lines 71‑77). This is the primary mechanism for adjusting log levels without affecting the global application.

### Fiber Lifecycle and State Tracking

Each plugin instance runs inside a **Fiber**, defined in [`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts) (lines 78‑166). The fiber manages the plugin lifecycle through state transitions and effect registration:

- **Instantiation** – When a plugin loads, the fiber constructor (lines 22‑36) initializes a `_runner` object holding the current epoch and an `execute` function that invokes the plugin’s callback.
- **State Machine** – The fiber transitions through `PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED` via `_setEpoch` (lines 99‑115). These changes are emitted via `ctx.emit('internal/status', …)` (line 60), allowing you to listen for lifecycle events.
- **Error Capture** – Errors are stored in `fiber._error` and propagated to the logger (lines 174‑177). The `composeError` utility (line 30) wraps effect execution and attaches an outer stack trace for easier debugging.

### LoggerService and Message Buffering

The **LoggerService** in [`packages/core/src/logger.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/logger.ts) (lines 12‑46) creates per-plugin `Logger` objects that buffer and forward messages to registered exporters. Key implementation details include:

- **Configuration Resolution** – `_resolveConfig()` merges intercept configurations from the current context chain (lines 14‑24), allowing specific plugins to override global log levels.
- **Message Structure** – When you call `logger.debug()`, the service builds a `Message` object containing a sequence number (`sn`), timestamp (`ts`), and call location (lines 38‑45).
- **Level Filtering** – Exporters iterate through registered handlers, and each exporter’s `level` property determines if the message is emitted (lines 41‑44). The service supports standard levels: `error`, `warn`, `info`, and `debug` (level 3).

### Dynamic Resolution with createResolve

For module-related debugging, [`packages/loader/src/resolve.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/resolve.ts) (lines 31‑63) provides the `createResolve` helper. This function determines the nearest [`package.json`](https://github.com/cordiverse/cordis/blob/main/package.json) directory and writes a hidden `.cordis/resolve.mjs` file that uses `import.meta.resolve` to handle specifiers relative to the plugin’s scope. This ensures consistent resolution during hot-module-replacement (HMR) cycles.

## Practical Debugging Strategies

### 1. Enable Debug Logging for Specific Plugins

Instead of flooding your console with global debug output, use `ctx.intercept` to target specific plugins:

```ts
// Set debug level (3) for this plugin only
ctx.intercept('logger', { level: 3 })

```

The `LoggerService._resolveConfig()` method merges this intercept configuration with the global context chain, ensuring that `logger.debug()` calls appear in your output while other plugins remain at their default levels.

### 2. Inspect Fiber State at Runtime

Access the fiber instance through `ctx.fiber` to examine the internal state machine:

```ts
const fiber = ctx.fiber
console.log('Current state:', fiber.state)      // FiberState enum value
console.log('Captured error:', fiber._error)     // Error object or null
console.log('Active effects:', fiber.getEffects())

```

The `state` property reflects the current lifecycle phase (`LOADING`, `ACTIVE`, etc.) as updated by `_updateState` (lines 55‑63). Checking `_error` immediately after a plugin failure reveals the captured exception before it propagates.

### 3. Track Effect Lifecycles with Labels

Effects registered via `Fiber.effect()` (lines 75‑84 and 120‑127) return disposables that include `EffectMeta` metadata. Wrap critical code with labeled effects to trace hierarchical relationships:

```ts
ctx.effect(() => {
  // Your initialization code here
  return () => {
    // Cleanup logic
  }
}, 'database-connection')

```

If an unhandled rejection occurs, the fiber logs the error with the effect label attached, making it easier to identify which disposable failed during the `UNLOADING` phase.

### 4. Verify Module Resolution Paths

When debugging import failures or HMR issues, use the resolver directly to confirm paths resolve correctly within the plugin’s scope:

```ts
import { createResolve } from '@cordis/loader'

const resolve = await createResolve(import.meta.url)
const resolvedPath = resolve('./config/schema.ts')
console.log('Resolved to:', resolvedPath)

```

This helper respects the plugin’s [`package.json`](https://github.com/cordiverse/cordis/blob/main/package.json) boundary and ensures that relative specifiers resolve consistently across HMR cycles.

### 5. Capture Full Stack Traces from Effect Errors

Errors thrown inside effects are processed by `composeError`, which calls `buildOuterStack` from the utilities to append an outer trace. When catching errors, inspect the full `error.stack` property:

```ts
try {
  await someEffect()
} catch (err) {
  console.error(err.stack)  // Contains both inner and outer stack traces
}

```

This enriched trace shows exactly which effect in the fiber hierarchy triggered the failure.

## Debug Code Examples

### Enable Verbose Logging for a Single Plugin

```ts
import { Context } from 'cordis'

export function apply(ctx: Context) {
  // Isolate logger config to debug level for this scope only
  ctx.intercept('logger', { name: 'data-processor', level: 3 })
  
  const logger = ctx.logger('data-processor')
  logger.debug('Processing batch started')
  logger.debug('Configuration loaded:', ctx.config)
}

```

### Inspect Fiber During Plugin Initialization

```ts
import { Context } from 'cordis'

export function apply(ctx: Context) {
  ctx.effect(() => {
    const fiber = ctx.fiber
    console.log('--- Fiber Debug Snapshot ---')
    console.log('State:', fiber.state)           // e.g., FiberState.LOADING
    console.log('UID:', fiber.uid)
    console.log('Epoch:', fiber._runner?.epoch)
    
    // Return cleanup function
    return () => console.log('Effect disposed')
  }, 'debug-snapshot')
}

```

### Resolve Modules Within Plugin Scope

```ts
import { Context } from 'cordis'
import { createResolve } from '@cordis/loader'

export async function apply(ctx: Context) {
  const resolve = await createResolve(import.meta.url)
  
  if (resolve) {
    const utilsPath = resolve('./utils/helpers')
    ctx.logger.debug('Resolved utils path:', utilsPath)
    
    // Dynamic import using resolved path
    const helpers = await import(utilsPath)
  }
}

```

## Summary

Debugging Cordis effectively relies on understanding its fiber-based architecture and leveraging its built-in instrumentation:

- **Use `ctx.intercept('logger', { level: 3 })`** to enable debug output for specific plugins without affecting global log levels.
- **Access `ctx.fiber`** to inspect the current lifecycle state (`LOADING`, `ACTIVE`, etc.) and check `_error` for captured exceptions.
- **Label your effects** with `ctx.effect(callback, 'label')` to trace which disposables fail during cleanup.
- **Utilize `createResolve`** from `@cordis/loader` to verify that dynamic imports resolve correctly within the plugin’s package scope.
- **Inspect `error.stack`** on caught exceptions to view the full trace including outer stacks added by `composeError`.

## Frequently Asked Questions

### How do I enable debug logging for only one plugin in Cordis?

Use the `ctx.intercept()` method to override the logger configuration for that specific context scope. Pass `{ level: 3 }` to set the level to `DEBUG` (where 0=error, 1=warn, 2=info, 3=debug). The `LoggerService._resolveConfig()` method in [`packages/core/src/logger.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/logger.ts) merges this intercept with the global configuration, ensuring only that plugin emits debug messages.

### What information does `ctx.fiber.state` provide?

The `state` property returns a `FiberState` enum value indicating the plugin’s current lifecycle phase: `PENDING`, `LOADING`, `ACTIVE`, `UNLOADING`, or `DISPOSED`. This state is updated internally by `_setEpoch` in [`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts) and emitted via the `internal/status` event, allowing you to track exactly when a plugin finishes initialization or begins teardown.

### How can I identify which effect is causing a disposal error?

Wrap your effect code with a descriptive label as the second argument to `ctx.effect()`. If the effect throws during execution or cleanup, Cordis logs the error with this label attached to the `EffectMeta` metadata. You can also call `fiber.getEffects()` to retrieve the list of active effect disposables and their associated metadata.

### Why are my dynamic imports failing in Cordis plugins?

Dynamic imports may fail if they resolve outside the plugin’s package scope or if the specifier is incorrect relative to the source file. Use `createResolve(import.meta.url)` from `@cordis/loader` to obtain a resolver function that respects the nearest [`package.json`](https://github.com/cordiverse/cordis/blob/main/package.json) boundary. This ensures consistent module resolution during both normal execution and HMR reloads.