bun-workers — Claude Code skill: Bun workers, install guide
A skill that tells the agent how to run Web Workers and worker_threads in Bun: parallel jobs, worker pools, message passing.
npx skills add secondsky/claude-skills --skill bun-workers -a claude-codeWhat bun-workers does
The skill kicks in when a Bun project needs heavy work moved off the main thread. It ships TypeScript examples: spawning a worker with new Worker(new URL("./worker.ts", import.meta.url)), handling onmessage and onerror, sending data with postMessage.
Separate sections cover transferable objects for zero-copy moves, SharedArrayBuffer with Atomics, and the Node.js worker_threads module with isMainThread, parentPort and workerData. There is a WorkerPool class with a task queue and a parallelMap example.
One gotcha worth knowing: when you pass an ArrayBuffer in the transfer list, it is detached in the sender, so touching it after postMessage fails. Shared memory needs SharedArrayBuffer, and you guard access with Atomics.
What it helps with
- move a CPU-heavy computation off the main thread
- build a worker pool with a task queue
- pass a large buffer without copying it
- wire up messaging between two workers
- report progress from a long-running job
Example prompts for the agent
- Set up a Web Worker in Bun to process a large array of numbers
- Build a worker_threads pool with a task queue
- Send an ArrayBuffer to a worker with a transfer list, no copy
How to install bun-workers
1Skills CLI (Vercel)
npx skills add secondsky/claude-skills --skill bun-workers -a claude-code2Manually: put the files in place
~/.claude/skills/bun-workers/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/bun-workers/
1Skills CLI (Vercel)
npx skills add secondsky/claude-skills --skill bun-workers -a codex2Manually: put the files in place
.agents/skills/bun-workers/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add secondsky/claude-skills --skill bun-workers -a cursor2Manually: put the files in place
.cursor/skills/bun-workers/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install secondsky/claude-skills bun-workers2Manually: put the files in place
.github/skills/bun-workers/SKILL.md1Skills CLI (Vercel)
npx skills add secondsky/claude-skills --skill bun-workers -a gemini-cli2Platform CLI
gemini skills install https://github.com/secondsky/claude-skills --path plugins/bun/skills/bun-workersManually: put the files in place
.devin/skills/bun-workers/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add secondsky/claude-skills --skill bun-workers -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about bun-workers
How do I install bun-workers in Claude?
Run npx skills add secondsky/claude-skills --skill bun-workers -a claude-code. The skill file then lands at ~/.claude/skills/bun-workers/SKILL.md.
What does bun-workers do?
It gives the agent patterns for parallel execution in Bun: Web Workers, worker_threads, a worker pool, transferable objects and shared memory.
How do Web Workers differ from worker_threads here?
Web Workers use postMessage and onmessage in browser style, while worker_threads uses isMainThread, parentPort and workerData. Both are covered with examples.
Source text
SKILL.md · MIT
---
name: bun-workers
description: Use for Web Workers in Bun, worker_threads, parallel processing, and background tasks.
metadata:
version: "1.0.0"
license: MIT
---
# Bun Workers
Bun supports Web Workers and Node.js worker_threads for parallel execution.
## Web Workers
### Basic Usage
```typescript
// main.ts
const worker = new Worker(new URL("./worker.ts", import.meta.url));
worker.postMessage({ type: "start", data: [1, 2, 3, 4, 5] });
worker.onmessage = (event) => {
console.log("Result:", event.data);
};
worker.onerror = (error) => {
console.error("Worker error:", error.message);
};
// worker.ts
self.onmessage = (event) => {
const { type, data } = event.data;
if (type === "start") {
const result = data.map((x) => x * 2);
self.postMessage(result);
}
};
```
### Worker with URL
```typescript
// Import from file path
const worker = new Worker(new URL("./worker.ts", import.meta.url));
// Or with blob URL
const code = `
self.onmessage = (e) => {
self.postMessage(e.data * 2);
};
`;
const blob = new Blob([code], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
```
### Transferable Objects
```typescript
// main.ts
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
const view = new Uint8Array(buffer);
view.fill(42);
// Transfer ownership (zero-copy)
worker.postMessage({ buffer }, [buffer]);
// buffer is now detached (empty)
// worker.ts
self.onmessage = (event) => {
const { buffer } = event.data;
const view = new Uint8Array(buffer);
// Process buffer...
// Transfer back
self.postMessage({ buffer }, [buffer]);
};
```
### Shared Memory
```typescript
// main.ts
const shared = new SharedArrayBuffer(1024);
const view = new Int32Array(shared);
worker.postMessage({ shared });
// Both main and worker can access
Atomics.add(view, 0, 1);
// worker.ts
self.onmessage = (event) => {
const { shared } = event.data;
const view = new Int32Array(shared);
// Atomic operations for thread safety
Atomics.add(view, 0, 1);
Atomics.notify(view, 0);
};
```
## Node.js worker_threads
```typescript
// main.ts
import { Worker, isMainThread, parentPort, workerData } from "worker_threads";
if (isMainThread) {
const worker = new Worker(import.meta.filename, {
workerData: { numbers: [1, 2, 3, 4, 5] },
});
worker.on("message", (result) => {
console.log("Result:", result);
});
worker.on("error", (err) => {
console.error("Error:", err);
});
worker.on("exit", (code) => {
console.log("Worker exited with code:", code);
});
} else {
// Worker code
const { numbers } = workerData;
const sum = numbers.reduce((a, b) => a + b, 0);
parentPort?.postMessage(sum);
}
```
### Worker Pool
```typescript
// worker-pool.ts
import { Worker } from "worker_threads";
class WorkerPool {
private workers: Worker[] = [];
private queue: Array<{
task: any;
resolve: (value: any) => void;
reject: (err: Error) => void;
}> = [];
private activeWorkers = new Set<Worker>();
constructor(
private workerPath: string,
private poolSize: number
) {
for (let i = 0; i < poolSize; i++) {
this.addWorker();
}
}
private addWorker() {
const worker = new Worker(this.workerPath);
worker.on("message", (result) => {
this.activeWorkers.delete(worker);
this.processQueue();
});
worker.on("error", (err) => {
this.activeWorkers.delete(worker);
console.error("Worker error:", err);
});
this.workers.push(worker);
}
async execute(task: any): Promise<any> {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
this.processQueue();
});
}
private processQueue() {
for (const worker of this.workers) {
if (!this.activeWorkers.has(worker) && this.queue.length > 0) {
const { task, resolve, reject } = this.queue.shift()!;
this.activeWorkers.add(worker);
worker.once("message", resolve);
worker.once("error", reject);
worker.postMessage(task);
}
}
}
terminate() {
this.workers.forEach((w) => w.terminate());
}
}
// Usage
const pool = new WorkerPool("./worker.ts", 4);
const results = await Promise.all([
pool.execute({ task: 1 }),
pool.execute({ task: 2 }),
pool.execute({ task: 3 }),
]);
pool.terminate();
```
## Patterns
### CPU-Intensive Tasks
```typescript
// main.ts
const worker = new Worker(new URL("./cpu-worker.ts", import.meta.url));
// Process large dataset
const data = Array.from({ length: 1000000 }, () => Math.random());
worker.postMessage({ type: "process", data });
worker.onmessage = (event) => {
if (event.data.type === "progress") {
console.log(`Progress: ${event.data.percent}%`);
} else if (event.data.type === "result") {
console.log("Done:", event.data.result);
}
};
// cpu-worker.ts
self.onmessage = (event) => {
const { type, data } = event.data;
if (type === "process") {
const chunkSize = 10000;
let result = 0;
for (let i = 0; i < data.length; i++) {
result += Math.sqrt(data[i]);
// Report progress
if (i % chunkSize === 0) {
self.postMessage({
type: "progress",
percent: Math.round((i / data.length) * 100),
});
}
}
self.postMessage({ type: "result", result });
}
};
```
### Parallel Map
```typescript
async function parallelMap<T, R>(
items: T[],
fn: string, // Function name in worker
workerUrl: URL,
concurrency = 4
): Promise<R[]> {
const results: R[] = new Array(items.length);
const workers: Worker[] = [];
// Create workers
for (let i = 0; i < concurrency; i++) {
workers.push(new Worker(workerUrl));
}
// Process items
let nextIndex = 0;
const processNext = (worker: Worker): Promise<void> => {
return new Promise((resolve) => {
if (nextIndex >= items.length) {
resolve();
return;
}
const index = nextIndex++;
worker.postMessage({ fn, item: items[index], index });
worker.onmessage = (event) => {
results[event.data.index] = event.data.result;
processNext(worker).then(resolve);
};
});
};
await Promise.all(workers.map(processNext));
workers.forEach((w) => w.terminate());
return results;
}
```
### Message Channel
```typescript
// Create channel for worker-to-worker communication
const channel = new MessageChannel();
const worker1 = new Worker(new URL("./worker1.ts", import.meta.url));
const worker2 = new Worker(new URL("./worker2.ts", import.meta.url));
// Give each worker a port
worker1.postMessage({ port: channel.port1 }, [channel.port1]);
worker2.postMessage({ port: channel.port2 }, [channel.port2]);
// worker1.ts
let port: MessagePort;
self.onmessage = (event) => {
if (event.data.port) {
port = event.data.port;
port.onmessage = (e) => console.log("From worker2:", e.data);
port.postMessage("Hello from worker1!");
}
};
```
## Error Handling
```typescript
const worker = new Worker(new URL("./worker.ts", import.meta.url));
worker.onerror = (error) => {
console.error("Uncaught error in worker:", error.message);
error.preventDefault(); // Prevent bubbling
};
worker.onmessageerror = (event) => {
console.error("Message deserialization failed");
};
// In worker
self.onerror = (error) => {
self.postMessage({ type: "error", message: error.message });
};
```
## Termination
```typescript
const worker = new Worker(new URL("./worker.ts", import.meta.url));
// Request graceful shutdown
worker.postMessage({ type: "shutdown" });
// Force terminate after timeout
setTimeout(() => {
worker.terminate();
}, 5000);
// In worker
self.onmessage = (event) => {
if (event.data.type === "shutdown") {
// Cleanup
self.close();
}
};
```
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| `Worker not found` | Wrong URL | Check worker file path |
| `Cannot serialize` | Non-transferable data | Use transferable objects |
| `DataCloneError` | Functions/DOM in message | Send only serializable data |
| `Worker terminated` | Premature terminate | Check termination logic |
## When to Load References
Load `references/optimization.md` when:
- Worker pool tuning
- Memory management
- Performance profiling
Load `references/patterns.md` when:
- Complex coordination
- Backpressure handling
- Error recovery
© secondsky · MIT · secondsky/claude-skills