# How Ephemeral Workers Are Spawned from Slack and Webhooks in Munder-Difflin: Gated Worktree Teardown Safety

> Learn how Munder-Difflin spawns ephemeral workers from Slack webhooks and uses gated teardown safety logic for Git worktrees. Understand the process and ensure secure cleanup.

- Repository: [Chaitanya Giri/munder-difflin](https://github.com/chaitanyagiri/munder-difflin)
- Tags: internals
- Published: 2026-08-29

---

**Munder-Difflin spawns isolated ephemeral workers by converting verified Slack webhook payloads into JSON spawn requests that create temporary Git worktrees, then enforces a gated teardown protocol requiring explicit "done" signals and clean Git status checks before safely removing or preserving the worktree.**

Munder-Difflin implements a "god-triggered" ephemeral worker architecture where incoming Slack messages serve as the entry point for short-lived compute tasks. The system creates isolated Git worktrees for each worker and maintains strict safety invariants during teardown to prevent accidental loss of unintegrated changes.

## Spawning Ephemeral Workers from Slack Webhooks

The spawning process begins with webhook verification and ends with an isolated worker process operating in its own Git worktree.

### Slack Events API Handling

Incoming Slack interactions are handled by the `SlackWebhookServer` class defined in [`src/main/slack.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/slack.ts). The server binds to a local HTTP port and verifies each request using the Slack signing secret through the `SlackWebhookServer.verify()` method located at line 86. Once verified, the server invokes the `onMessage` callback registered by the main process, forwarding the message text and metadata for worker creation.

### Spawn Request Manifest Creation

The `onMessage` callback generates a **spawn-request JSON file** in the `<HIVE_ROOT>/spawn-requests/` directory. The manifest specification, defined in [`src/main/hive.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/hive.ts) around line 1412, requires at minimum an `objective` field describing the work and a `cwd` field specifying the repository path. Optional fields include `name`, `command`, `provider`, `tokenCap`, and a `slack` object containing `channel` and `thread_ts` properties that route error messages back to the originating thread.

The main process in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) polls the `spawn-requests` directory and creates the worker environment under `<HIVE_ROOT>/workers/worker-<id>`. Each worker receives an ephemeral client secret minted by the Realtime-Michael endpoint and operates in an isolated worktree with `isolate: true` enforced by default.

### Worker Tracking and Metadata

Spawned workers are tracked in the `liveEphemeralWorkers` map, which stores metadata including the Slack thread identifier and worktree path. According to [`src/renderer/src/store/store.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/store/store.ts), the system persists the `channel` and `thread_ts` values on messages enqueued for the worker, ensuring failures can be reported to the correct Slack thread.

## Gated Worktree Teardown Safety Logic

When a worker finishes, the system invokes a multi-layered safety protocol before removing the worktree. This **gated teardown** ensures that work is either integrated by the human operator or deliberately preserved before deletion.

### Initiating Stop Requests

The teardown process begins through `manualStopEphemeralWorker(id)`, exposed in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) and accessible via the `slack:stop` IPC channel from the renderer process. The handler first validates that the worker's `scratchDir` still exists and that the worker is marked as live in the `liveEphemeralWorkers` map.

### Verifying the "Done" State

Before any destructive action, the system verifies that the worker has explicitly signaled completion. The worker must send an outbox message with `act: "done"` handled by the `informGod` function in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts). This message sets `worker.done = true`. If this flag is not set, the stop request is rejected and the worktree is preserved, preventing premature deletion of active work.

### Git Diff Checks and Failure Preservation

When the done state is confirmed, the system executes `git diff --quiet` to check for uncommitted changes. If pending modifications exist that have not been merged by the human operator, the worktree is moved to the preservation area at `$HIVE_ROOT/preserved/` rather than deleted. Only worktrees with clean Git status proceed to deletion via `fs.rmSync(..., { recursive: true })`, wrapped in try-catch blocks to prevent main process crashes.

### Background Garbage Collection

A periodic **ephemeral-worker GC sweep** runs in [`src/main/git.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/git.ts) to clean up preserved worktrees. This sweep only purges directories that both lack integration markers and are not flagged with `.done` without a corresponding Git commit. The sweep never touches worktrees that retain active worker metadata or unintegrated changes.

## Implementation Examples

### Triggering a Worker from Slack

```typescript
// src/main/slack.ts - Slack webhook registration
const slackServer = new SlackWebhookServer({
  port: config.slackPort,
  signingSecret: config.slackSigningSecret,
  channelId: config.slackChannelId,
  onMessage: async (msg) => {
    const request = {
      objective: msg.text,
      cwd: '/path/to/repo',
      slack: { channel: msg.channel, thread_ts: msg.thread_ts },
    };
    await writeFile(
      join(HIVE_ROOT, 'spawn-requests', `${uuidv4()}.json`),
      JSON.stringify(request, null, 2)
    );
  },
});
await slackServer.start();

```

### Signaling Completion from the Worker

```typescript
// Worker agent code
await hive.send({ 
  to: 'god', 
  act: 'inform', 
  subject: 'Job complete', 
  body: 'Result …' 
}, 'ephemeral-worker');

await hive.send({ 
  to: 'god', 
  act: 'done' 
}, 'ephemeral-worker'); // Required for teardown approval

```

### Manual Stop via IPC

```typescript
// Renderer process invoking gated teardown
await window.cth.manualStopEphemeralWorker('worker-abc123');

```

The IPC handler in [`src/preload/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/preload/index.ts) routes this call to the main process, which executes the gated teardown logic described above.

## Summary

- **Slack Integration**: [`src/main/slack.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/slack.ts) verifies webhook signatures and converts messages to JSON spawn requests in `<HIVE_ROOT>/spawn-requests/`.
- **Worker Isolation**: Each worker receives an isolated Git worktree under `<HIVE_ROOT>/workers/worker-<id>` and an ephemeral client secret.
- **Gated Teardown**: The `manualStopEphemeralWorker` function in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) enforces a strict protocol requiring the `act: "done"` signal and clean Git status.
- **Data Preservation**: Worktrees with uncommitted changes are relocated to `$HIVE_ROOT/preserved/` rather than deleted, with final cleanup handled by the GC sweep in [`src/main/git.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/git.ts).

## Frequently Asked Questions

### What happens if a worker crashes before signaling "done"?

If a worker process terminates without sending the `act: "done"` message, the `worker.done` flag remains false. The gated teardown logic in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) will reject any stop requests for this worker, preserving the worktree in its current state to prevent loss of partial progress.

### How does the system handle uncommitted changes during teardown?

Before deletion, the system runs `git diff --quiet` to detect pending changes. If the repository is dirty, the worktree is moved to `$HIVE_ROOT/preserved/` instead of being deleted. The background GC sweep in [`src/main/git.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/git.ts) will only remove these preserved directories after confirming they have been properly integrated or manually reviewed.

### Can spawn requests originate from webhooks other than Slack?

Yes. While [`src/main/slack.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/slack.ts) provides the Slack Events API integration, any process can create a valid spawn request by writing a JSON file to `<HIVE_ROOT>/spawn-requests/` containing the required `objective` and `cwd` fields. The main process polls this directory agnostically, enabling webhook endpoints from GitHub, GitLab, or custom services to trigger ephemeral workers.

### Where is the ephemeral worker's Slack thread metadata stored?

The Slack `channel` and `thread_ts` values are stored in the `slack` object within the spawn request JSON. Once the worker is spawned, this metadata is maintained in the `liveEphemeralWorkers` map alongside the worktree path, as referenced in [`src/renderer/src/store/store.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/store/store.ts), ensuring error messages route back to the correct thread.