opencode-skills-collection 4.0.45 → 4.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +25 -1
- package/bundled-skills/agy-delegate/SKILL.md +168 -0
- package/bundled-skills/agy-delegate/references/dispatch-and-poll.md +140 -0
- package/bundled-skills/agy-delegate/references/multi-task-queues.md +59 -0
- package/bundled-skills/agy-delegate/references/review-and-land.md +103 -0
- package/bundled-skills/agy-delegate/references/writing-the-brief.md +124 -0
- package/bundled-skills/aider-delegate/SKILL.md +227 -0
- package/bundled-skills/aider-delegate/references/dispatch-and-poll.md +154 -0
- package/bundled-skills/aider-delegate/references/multi-task-queues.md +68 -0
- package/bundled-skills/aider-delegate/references/review-and-land.md +101 -0
- package/bundled-skills/aider-delegate/references/writing-the-brief.md +142 -0
- package/bundled-skills/babysit-pr/SKILL.md +343 -0
- package/bundled-skills/claude-delegate/SKILL.md +182 -0
- package/bundled-skills/claude-delegate/references/dispatch-and-poll.md +220 -0
- package/bundled-skills/claude-delegate/references/multi-task-queues.md +66 -0
- package/bundled-skills/claude-delegate/references/review-and-land.md +117 -0
- package/bundled-skills/claude-delegate/references/writing-the-brief.md +158 -0
- package/bundled-skills/cline-delegate/SKILL.md +140 -0
- package/bundled-skills/cline-delegate/references/dispatch-and-poll.md +142 -0
- package/bundled-skills/cline-delegate/references/multi-task-queues.md +58 -0
- package/bundled-skills/cline-delegate/references/review-and-land.md +80 -0
- package/bundled-skills/cline-delegate/references/writing-the-brief.md +129 -0
- package/bundled-skills/codex-delegate/SKILL.md +177 -0
- package/bundled-skills/codex-delegate/references/dispatch-and-poll.md +161 -0
- package/bundled-skills/codex-delegate/references/multi-task-queues.md +66 -0
- package/bundled-skills/codex-delegate/references/review-and-land.md +126 -0
- package/bundled-skills/codex-delegate/references/writing-the-brief.md +125 -0
- package/bundled-skills/commandcode-delegate/SKILL.md +188 -0
- package/bundled-skills/commandcode-delegate/references/dispatch-and-poll.md +222 -0
- package/bundled-skills/commandcode-delegate/references/multi-task-queues.md +81 -0
- package/bundled-skills/commandcode-delegate/references/review-and-land.md +144 -0
- package/bundled-skills/commandcode-delegate/references/writing-the-brief.md +147 -0
- package/bundled-skills/copilot-delegate/SKILL.md +159 -0
- package/bundled-skills/copilot-delegate/references/dispatch-and-poll.md +143 -0
- package/bundled-skills/copilot-delegate/references/multi-task-queues.md +58 -0
- package/bundled-skills/copilot-delegate/references/review-and-land.md +80 -0
- package/bundled-skills/copilot-delegate/references/writing-the-brief.md +140 -0
- package/bundled-skills/cursor-delegate/SKILL.md +170 -0
- package/bundled-skills/cursor-delegate/references/dispatch-and-poll.md +162 -0
- package/bundled-skills/cursor-delegate/references/multi-task-queues.md +59 -0
- package/bundled-skills/cursor-delegate/references/review-and-land.md +93 -0
- package/bundled-skills/cursor-delegate/references/writing-the-brief.md +126 -0
- package/bundled-skills/debate-review/SKILL.md +85 -0
- package/bundled-skills/debate-review/assets/prompts/review-debate.md +51 -0
- package/bundled-skills/debate-review/assets/prompts/review-main.md +64 -0
- package/bundled-skills/debate-review/assets/prompts/review-rebuttal.md +42 -0
- package/bundled-skills/debate-review/references/comment-format.md +63 -0
- package/bundled-skills/debate-review/references/schema.md +95 -0
- package/bundled-skills/delegate-setup/SKILL.md +215 -0
- package/bundled-skills/delegate-setup/references/schema.md +100 -0
- package/bundled-skills/delegate-setup/references/setup-dialogue.md +101 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +1 -1
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/aas-core.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +3 -3
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/falsify/SKILL.md +263 -0
- package/bundled-skills/falsify/references/bias-catalog.md +42 -0
- package/bundled-skills/falsify/references/mental-models.md +135 -0
- package/bundled-skills/falsify/templates/thinking-ledger.md +104 -0
- package/bundled-skills/graceful-shutdown/SKILL.md +379 -0
- package/bundled-skills/grok-delegate/SKILL.md +179 -0
- package/bundled-skills/grok-delegate/references/dispatch-and-poll.md +171 -0
- package/bundled-skills/grok-delegate/references/multi-task-queues.md +67 -0
- package/bundled-skills/grok-delegate/references/review-and-land.md +130 -0
- package/bundled-skills/grok-delegate/references/writing-the-brief.md +119 -0
- package/bundled-skills/kimi-delegate/SKILL.md +137 -0
- package/bundled-skills/kimi-delegate/references/dispatch-and-poll.md +134 -0
- package/bundled-skills/kimi-delegate/references/multi-task-queues.md +58 -0
- package/bundled-skills/kimi-delegate/references/review-and-land.md +92 -0
- package/bundled-skills/kimi-delegate/references/writing-the-brief.md +132 -0
- package/bundled-skills/omp-delegate/SKILL.md +186 -0
- package/bundled-skills/omp-delegate/references/dispatch-and-poll.md +155 -0
- package/bundled-skills/omp-delegate/references/multi-task-queues.md +59 -0
- package/bundled-skills/omp-delegate/references/review-and-land.md +95 -0
- package/bundled-skills/omp-delegate/references/writing-the-brief.md +149 -0
- package/bundled-skills/opencode-delegate/SKILL.md +189 -0
- package/bundled-skills/opencode-delegate/references/dispatch-and-poll.md +156 -0
- package/bundled-skills/opencode-delegate/references/multi-task-queues.md +68 -0
- package/bundled-skills/opencode-delegate/references/review-and-land.md +125 -0
- package/bundled-skills/opencode-delegate/references/writing-the-brief.md +142 -0
- package/bundled-skills/pi-delegate/SKILL.md +148 -0
- package/bundled-skills/pi-delegate/references/dispatch-and-poll.md +146 -0
- package/bundled-skills/pi-delegate/references/multi-task-queues.md +59 -0
- package/bundled-skills/pi-delegate/references/review-and-land.md +95 -0
- package/bundled-skills/pi-delegate/references/writing-the-brief.md +132 -0
- package/bundled-skills/qoder-delegate/SKILL.md +151 -0
- package/bundled-skills/qoder-delegate/references/dispatch-and-poll.md +119 -0
- package/bundled-skills/qoder-delegate/references/multi-task-queues.md +43 -0
- package/bundled-skills/qoder-delegate/references/review-and-land.md +66 -0
- package/bundled-skills/qoder-delegate/references/writing-the-brief.md +85 -0
- package/bundled-skills/sandbase-mcp/SKILL.md +194 -0
- package/bundled-skills/ui-slop-score/SKILL.md +2 -2
- package/bundled-skills/unsloth-finetuning/SKILL.md +352 -0
- package/bundled-skills/vibe-delegate/SKILL.md +147 -0
- package/bundled-skills/vibe-delegate/references/dispatch-and-poll.md +144 -0
- package/bundled-skills/vibe-delegate/references/multi-task-queues.md +59 -0
- package/bundled-skills/vibe-delegate/references/review-and-land.md +87 -0
- package/bundled-skills/vibe-delegate/references/writing-the-brief.md +131 -0
- package/bundled-skills/warp-delegate/SKILL.md +192 -0
- package/bundled-skills/warp-delegate/references/dispatch-and-poll.md +163 -0
- package/bundled-skills/warp-delegate/references/multi-task-queues.md +80 -0
- package/bundled-skills/warp-delegate/references/review-and-land.md +123 -0
- package/bundled-skills/warp-delegate/references/writing-the-brief.md +105 -0
- package/bundled-skills/zcode-delegate/SKILL.md +202 -0
- package/bundled-skills/zcode-delegate/references/dispatch-and-poll.md +113 -0
- package/bundled-skills/zcode-delegate/references/multi-task-queues.md +87 -0
- package/bundled-skills/zcode-delegate/references/review-and-land.md +81 -0
- package/bundled-skills/zcode-delegate/references/writing-the-brief.md +120 -0
- package/package.json +1 -1
- package/skills_index.json +660 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graceful-shutdown
|
|
3
|
+
description: "Implement graceful shutdown for servers and workers: drain connections, finish in-flight work, release resources, and exit cleanly on SIGTERM/SIGINT."
|
|
4
|
+
category: development
|
|
5
|
+
risk: safe
|
|
6
|
+
source: self
|
|
7
|
+
source_type: self
|
|
8
|
+
date_added: "2026-08-27"
|
|
9
|
+
author: Prajeeth-12
|
|
10
|
+
tags: [graceful-shutdown, signals, SIGTERM, SIGINT, drain, health-check, kubernetes, docker, production, resilience]
|
|
11
|
+
tools: [claude, cursor, codex, gemini]
|
|
12
|
+
license: "MIT"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Graceful Shutdown
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
A skill for implementing graceful shutdown in servers, workers, and long-running processes. Ensures in-flight requests complete, background jobs finish or checkpoint, database connections close cleanly, and the process exits with a proper status code. Essential for zero-downtime deployments in container orchestrators (Kubernetes, ECS, Docker Compose) and bare-metal process managers (systemd, PM2).
|
|
20
|
+
|
|
21
|
+
## When to Use This Skill
|
|
22
|
+
|
|
23
|
+
- Use when building an HTTP server that must not drop active connections during deploys
|
|
24
|
+
- Use when writing a background worker that processes jobs from a queue
|
|
25
|
+
- Use when deploying to Kubernetes, Docker, or any environment that sends SIGTERM before killing
|
|
26
|
+
- Use when the user says "graceful shutdown", "drain connections", "handle SIGTERM", "zero downtime", or "don't kill active requests"
|
|
27
|
+
- Use when implementing health check endpoints (`/healthz`, `/readyz`) for orchestrators
|
|
28
|
+
|
|
29
|
+
## How It Works
|
|
30
|
+
|
|
31
|
+
### Step 1: Register signal handlers early
|
|
32
|
+
|
|
33
|
+
Trap `SIGTERM` (orchestrator shutdown) and `SIGINT` (Ctrl+C) at process startup. Set a flag so the application knows it is shutting down.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
let isShuttingDown = false;
|
|
37
|
+
|
|
38
|
+
function onShutdownSignal(signal: string): void {
|
|
39
|
+
if (isShuttingDown) return; // prevent double-shutdown
|
|
40
|
+
isShuttingDown = true;
|
|
41
|
+
console.log(`Received ${signal}, starting graceful shutdown...`);
|
|
42
|
+
shutdown();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
process.on("SIGTERM", () => onShutdownSignal("SIGTERM"));
|
|
46
|
+
process.on("SIGINT", () => onShutdownSignal("SIGINT"));
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Step 2: Stop accepting new work
|
|
50
|
+
|
|
51
|
+
Immediately stop the server from accepting new connections. For HTTP servers, call `server.close()`. For queue workers, stop polling for new jobs.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
async function shutdown(): Promise<void> {
|
|
55
|
+
// 1. Stop accepting new connections
|
|
56
|
+
server.close(() => {
|
|
57
|
+
console.log("Server closed — no new connections accepted");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 2. Mark health check as not-ready so load balancers stop routing
|
|
61
|
+
// (readiness probe returns 503 from this point)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Step 3: Drain in-flight work with a deadline
|
|
66
|
+
|
|
67
|
+
Wait for active requests and background tasks to finish, but enforce a hard deadline so the process never hangs indefinitely.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const DRAIN_TIMEOUT_MS = 25_000; // must be less than orchestrator's terminationGracePeriodSeconds
|
|
71
|
+
|
|
72
|
+
async function drainAndExit(): Promise<void> {
|
|
73
|
+
const deadline = setTimeout(() => {
|
|
74
|
+
console.error("Drain timeout reached — forcing exit");
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}, DRAIN_TIMEOUT_MS);
|
|
77
|
+
deadline.unref(); // don't keep the event loop alive just for the timer
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
// Wait for active connections to finish
|
|
81
|
+
await waitForActiveConnections();
|
|
82
|
+
|
|
83
|
+
// Flush buffered data (logs, metrics, queues)
|
|
84
|
+
await flushBuffers();
|
|
85
|
+
|
|
86
|
+
// Close external resource handles
|
|
87
|
+
await closeResources();
|
|
88
|
+
|
|
89
|
+
console.log("Graceful shutdown complete");
|
|
90
|
+
process.exit(0);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error("Error during shutdown:", err);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Step 4: Implement readiness and liveness probes
|
|
99
|
+
|
|
100
|
+
Orchestrators use these to decide whether to route traffic and whether to restart the container.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { createServer, IncomingMessage, ServerResponse } from "node:http";
|
|
104
|
+
|
|
105
|
+
function handleHealthCheck(req: IncomingMessage, res: ServerResponse): void {
|
|
106
|
+
if (req.url === "/healthz") {
|
|
107
|
+
// Liveness: is the process alive and not deadlocked?
|
|
108
|
+
res.writeHead(200).end("ok");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (req.url === "/readyz") {
|
|
113
|
+
// Readiness: should traffic be routed here?
|
|
114
|
+
if (isShuttingDown) {
|
|
115
|
+
res.writeHead(503).end("shutting down");
|
|
116
|
+
} else {
|
|
117
|
+
res.writeHead(200).end("ready");
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Step 5: Track active connections
|
|
125
|
+
|
|
126
|
+
Maintain a count of in-flight requests so you know when draining is complete.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
let activeConnections = 0;
|
|
130
|
+
let drainResolve: (() => void) | null = null;
|
|
131
|
+
|
|
132
|
+
function onRequestStart(): void {
|
|
133
|
+
activeConnections++;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function onRequestEnd(): void {
|
|
137
|
+
activeConnections--;
|
|
138
|
+
if (isShuttingDown && activeConnections === 0 && drainResolve) {
|
|
139
|
+
drainResolve();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function waitForActiveConnections(): Promise<void> {
|
|
144
|
+
if (activeConnections === 0) return Promise.resolve();
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
drainResolve = resolve;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Examples
|
|
152
|
+
|
|
153
|
+
### Example 1: Express.js server with graceful shutdown
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import express from "express";
|
|
157
|
+
import { createServer } from "node:http";
|
|
158
|
+
|
|
159
|
+
const app = express();
|
|
160
|
+
const server = createServer(app);
|
|
161
|
+
let isShuttingDown = false;
|
|
162
|
+
let activeRequests = 0;
|
|
163
|
+
|
|
164
|
+
// Track in-flight requests
|
|
165
|
+
app.use((req, res, next) => {
|
|
166
|
+
if (isShuttingDown) {
|
|
167
|
+
res.setHeader("Connection", "close");
|
|
168
|
+
res.status(503).json({ error: "Server is shutting down" });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
activeRequests++;
|
|
172
|
+
res.on("finish", () => activeRequests--);
|
|
173
|
+
next();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Health endpoints
|
|
177
|
+
app.get("/healthz", (_, res) => res.send("ok"));
|
|
178
|
+
app.get("/readyz", (_, res) => {
|
|
179
|
+
res.status(isShuttingDown ? 503 : 200).send(isShuttingDown ? "draining" : "ready");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Application routes
|
|
183
|
+
app.get("/api/data", async (req, res) => {
|
|
184
|
+
const data = await fetchData();
|
|
185
|
+
res.json(data);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Graceful shutdown
|
|
189
|
+
function shutdown(signal: string): void {
|
|
190
|
+
if (isShuttingDown) return;
|
|
191
|
+
isShuttingDown = true;
|
|
192
|
+
console.log(`${signal} received — draining ${activeRequests} active requests`);
|
|
193
|
+
|
|
194
|
+
server.close();
|
|
195
|
+
|
|
196
|
+
const forceExit = setTimeout(() => {
|
|
197
|
+
console.error("Forced exit — drain timeout exceeded");
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}, 25_000);
|
|
200
|
+
forceExit.unref();
|
|
201
|
+
|
|
202
|
+
const poll = setInterval(() => {
|
|
203
|
+
if (activeRequests === 0) {
|
|
204
|
+
clearInterval(poll);
|
|
205
|
+
console.log("All requests drained — exiting cleanly");
|
|
206
|
+
process.exit(0);
|
|
207
|
+
}
|
|
208
|
+
}, 100);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
212
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
213
|
+
|
|
214
|
+
server.listen(3000, () => console.log("Server ready on :3000"));
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Example 2: Python FastAPI with graceful shutdown
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import asyncio
|
|
221
|
+
import signal
|
|
222
|
+
from contextlib import asynccontextmanager
|
|
223
|
+
from fastapi import FastAPI, Request, Response
|
|
224
|
+
|
|
225
|
+
active_requests = 0
|
|
226
|
+
is_shutting_down = False
|
|
227
|
+
shutdown_event = asyncio.Event()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@asynccontextmanager
|
|
231
|
+
async def lifespan(app: FastAPI):
|
|
232
|
+
# Startup
|
|
233
|
+
loop = asyncio.get_event_loop()
|
|
234
|
+
loop.add_signal_handler(signal.SIGTERM, begin_shutdown)
|
|
235
|
+
yield
|
|
236
|
+
# Shutdown — wait for in-flight requests
|
|
237
|
+
if active_requests > 0:
|
|
238
|
+
try:
|
|
239
|
+
await asyncio.wait_for(shutdown_event.wait(), timeout=25.0)
|
|
240
|
+
except asyncio.TimeoutError:
|
|
241
|
+
print(f"Drain timeout — {active_requests} requests abandoned")
|
|
242
|
+
print("Shutdown complete")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
app = FastAPI(lifespan=lifespan)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def begin_shutdown():
|
|
249
|
+
global is_shutting_down
|
|
250
|
+
is_shutting_down = True
|
|
251
|
+
print(f"SIGTERM received — draining {active_requests} requests")
|
|
252
|
+
if active_requests == 0:
|
|
253
|
+
shutdown_event.set()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@app.middleware("http")
|
|
257
|
+
async def track_requests(request: Request, call_next):
|
|
258
|
+
global active_requests
|
|
259
|
+
if is_shutting_down:
|
|
260
|
+
return Response("Service shutting down", status_code=503)
|
|
261
|
+
active_requests += 1
|
|
262
|
+
try:
|
|
263
|
+
response = await call_next(request)
|
|
264
|
+
return response
|
|
265
|
+
finally:
|
|
266
|
+
active_requests -= 1
|
|
267
|
+
if is_shutting_down and active_requests == 0:
|
|
268
|
+
shutdown_event.set()
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@app.get("/healthz")
|
|
272
|
+
async def healthz():
|
|
273
|
+
return {"status": "ok"}
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@app.get("/readyz")
|
|
277
|
+
async def readyz():
|
|
278
|
+
if is_shutting_down:
|
|
279
|
+
return Response("draining", status_code=503)
|
|
280
|
+
return {"status": "ready"}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Example 3: Background worker with checkpoint
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { parentPort } from "node:worker_threads";
|
|
287
|
+
|
|
288
|
+
let isShuttingDown = false;
|
|
289
|
+
let currentJob: { id: string; checkpoint: () => Promise<void> } | null = null;
|
|
290
|
+
|
|
291
|
+
process.on("SIGTERM", async () => {
|
|
292
|
+
isShuttingDown = true;
|
|
293
|
+
console.log("Worker shutting down — finishing current job");
|
|
294
|
+
|
|
295
|
+
if (currentJob) {
|
|
296
|
+
await currentJob.checkpoint();
|
|
297
|
+
console.log(`Job ${currentJob.id} checkpointed`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
process.exit(0);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
async function processJobs(queue: JobQueue): Promise<void> {
|
|
304
|
+
while (!isShuttingDown) {
|
|
305
|
+
const job = await queue.poll({ timeout: 5000 });
|
|
306
|
+
if (!job) continue;
|
|
307
|
+
|
|
308
|
+
currentJob = job;
|
|
309
|
+
await job.execute();
|
|
310
|
+
await queue.ack(job.id);
|
|
311
|
+
currentJob = null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Best Practices
|
|
317
|
+
|
|
318
|
+
- Always set a drain timeout shorter than the orchestrator's kill timeout (`terminationGracePeriodSeconds` in Kubernetes defaults to 30s — use 25s for your drain)
|
|
319
|
+
- Return `Connection: close` header on responses sent during draining so HTTP/1.1 clients don't reuse the connection
|
|
320
|
+
- Reject new requests with 503 during shutdown so load balancers learn faster
|
|
321
|
+
- Unref your force-exit timer so it doesn't keep the event loop alive after all work is done
|
|
322
|
+
- Flush async buffers (log transports, metric aggregators, write-ahead logs) before exiting
|
|
323
|
+
- Use `process.exit(0)` for clean shutdown and `process.exit(1)` for timeout/error so orchestrators can distinguish the two
|
|
324
|
+
- Test shutdown behavior explicitly — simulate SIGTERM in integration tests and verify no requests are dropped
|
|
325
|
+
|
|
326
|
+
## Common Pitfalls
|
|
327
|
+
|
|
328
|
+
- **Problem:** Kubernetes kills the pod before connections drain because `terminationGracePeriodSeconds` is too short.
|
|
329
|
+
**Solution:** Set it to at least drain timeout + 5s buffer. If your longest request takes 60s, use `terminationGracePeriodSeconds: 70` and drain timeout of 65s.
|
|
330
|
+
|
|
331
|
+
- **Problem:** Load balancer keeps sending traffic after SIGTERM because readiness probe still returns 200.
|
|
332
|
+
**Solution:** Flip the readiness probe to 503 immediately on signal receipt — before starting to drain.
|
|
333
|
+
|
|
334
|
+
- **Problem:** `server.close()` resolves instantly but connections remain open (keep-alive).
|
|
335
|
+
**Solution:** Track connections manually and destroy idle keep-alive sockets on shutdown. Active sockets with in-flight requests should drain normally.
|
|
336
|
+
|
|
337
|
+
- **Problem:** Double shutdown from both SIGTERM and SIGINT (e.g., Docker sends SIGTERM then user hits Ctrl+C).
|
|
338
|
+
**Solution:** Guard with a `isShuttingDown` flag — ignore the second signal.
|
|
339
|
+
|
|
340
|
+
- **Problem:** Deadlocked process never exits because drain waits forever.
|
|
341
|
+
**Solution:** Always have a hard force-exit timeout as the final backstop.
|
|
342
|
+
|
|
343
|
+
## Kubernetes Configuration
|
|
344
|
+
|
|
345
|
+
```yaml
|
|
346
|
+
apiVersion: apps/v1
|
|
347
|
+
kind: Deployment
|
|
348
|
+
spec:
|
|
349
|
+
template:
|
|
350
|
+
spec:
|
|
351
|
+
terminationGracePeriodSeconds: 30
|
|
352
|
+
containers:
|
|
353
|
+
- name: app
|
|
354
|
+
livenessProbe:
|
|
355
|
+
httpGet:
|
|
356
|
+
path: /healthz
|
|
357
|
+
port: 3000
|
|
358
|
+
initialDelaySeconds: 5
|
|
359
|
+
periodSeconds: 10
|
|
360
|
+
readinessProbe:
|
|
361
|
+
httpGet:
|
|
362
|
+
path: /readyz
|
|
363
|
+
port: 3000
|
|
364
|
+
initialDelaySeconds: 2
|
|
365
|
+
periodSeconds: 5
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Limitations
|
|
369
|
+
|
|
370
|
+
- This skill does not replace environment-specific validation, testing, or expert review.
|
|
371
|
+
- WebSocket and SSE connections require application-level close frames before severing — `server.close()` alone won't gracefully end them.
|
|
372
|
+
- In clustered/multi-process setups (e.g., Node.js `cluster` module), each worker must handle signals independently.
|
|
373
|
+
- Some cloud platforms (Heroku, Railway) send SIGTERM with very short grace periods (10-30s) — adjust drain timeouts accordingly.
|
|
374
|
+
|
|
375
|
+
## Related Skills
|
|
376
|
+
|
|
377
|
+
- `@api-rate-limit-handler` — Resilient retry and backoff for outbound requests
|
|
378
|
+
- `@circuit-breaker` — When to stop retrying entirely and fail fast
|
|
379
|
+
- `@error-handling` — Structured error handling patterns
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grok-delegate
|
|
3
|
+
description: Delegate coding tasks to the Grok Build CLI only when the user explicitly
|
|
4
|
+
requests it, while the orchestrator retains review and landing responsibility.
|
|
5
|
+
risk: safe
|
|
6
|
+
category: agent-orchestration
|
|
7
|
+
source: https://github.com/amElnagdy/delegate-skills
|
|
8
|
+
source_repo: amElnagdy/delegate-skills
|
|
9
|
+
source_type: community
|
|
10
|
+
date_added: '2026-08-26'
|
|
11
|
+
license: MIT
|
|
12
|
+
license_source: https://github.com/amElnagdy/delegate-skills/blob/master/LICENSE
|
|
13
|
+
compatibility: Requires the `grok` CLI (Grok Build) installed and authenticated (`grok
|
|
14
|
+
login`, or `XAI_API_KEY`; beta access needs an eligible xAI subscription), Node
|
|
15
|
+
18+, and git. The orchestrating agent must be able to run shell commands and read
|
|
16
|
+
files. Shell examples assume bash/zsh (macOS/Linux, or Git Bash/WSL on Windows).
|
|
17
|
+
metadata:
|
|
18
|
+
version: 0.5.0
|
|
19
|
+
---
|
|
20
|
+
# Grok Delegate
|
|
21
|
+
|
|
22
|
+
## When to Use
|
|
23
|
+
|
|
24
|
+
- You want to delegate a bounded coding task to a separate `grok` implementer (`Grok Build`) and then review its diff yourself.
|
|
25
|
+
- The user explicitly asked for delegation to this implementer.
|
|
26
|
+
|
|
27
|
+
You are the **orchestrator**. This skill lets you hand a bounded coding task to a separate
|
|
28
|
+
**implementer** — the Grok Build CLI (`grok`) — then review what it produced and land it yourself. You
|
|
29
|
+
write the brief and own the judgment; Grok does the typing under an explicit autonomy profile; you
|
|
30
|
+
verify and commit.
|
|
31
|
+
|
|
32
|
+
Nothing here is specific to one orchestrating agent. The loop needs only the ability to run a shell
|
|
33
|
+
command and read a file, so it works the same whether you are Claude Code, Cursor, OpenCode with a
|
|
34
|
+
selected model, or any comparable agent. (It is designed for Claude Code and Cursor; treat other
|
|
35
|
+
orchestrators as designed-for, not yet proven.)
|
|
36
|
+
|
|
37
|
+
## When NOT to use this
|
|
38
|
+
|
|
39
|
+
- The task is small enough to just do inline — delegation overhead is not worth it.
|
|
40
|
+
- The `grok` CLI is not installed, not authenticated, or the account lacks Grok Build beta access.
|
|
41
|
+
- You want to write the code yourself, or you only need a review without an implementer run.
|
|
42
|
+
|
|
43
|
+
## Prerequisites (check once)
|
|
44
|
+
|
|
45
|
+
1. `grok version` succeeds. If not, install on any platform with
|
|
46
|
+
`npm i -g @xai-official/grok` (or use the installer from xAI's official Grok CLI docs) and
|
|
47
|
+
authenticate (`grok login`, or `grok login --device-auth` on headless hosts, or set
|
|
48
|
+
`XAI_API_KEY`).
|
|
49
|
+
2. **Confirm which `grok` is on PATH.** `command -v grok` shows the active binary and `grok version`
|
|
50
|
+
its version — the relay records the version it ran into `result.json`, so a stale binary is visible
|
|
51
|
+
after the fact.
|
|
52
|
+
3. You are in (or will point `--cd` at) the target git repository.
|
|
53
|
+
|
|
54
|
+
## The loop
|
|
55
|
+
|
|
56
|
+
Run these five steps per task. Steps 1, 4, and 5 are your judgment; 2 and 3 are mechanical.
|
|
57
|
+
|
|
58
|
+
### 1. Write the brief
|
|
59
|
+
|
|
60
|
+
Grok sees **only** the text you send — no orchestrator chat history, no shared context. Everything the
|
|
61
|
+
task needs goes in the brief: the goal, the current state, what to change, what to leave untouched,
|
|
62
|
+
the project's **actual** gate commands (discover them from the repo's CLAUDE.md/AGENTS.md/Makefile —
|
|
63
|
+
do not assume), and a report contract. Tell Grok it will **not** commit (you will). Keep one task per
|
|
64
|
+
brief. Full guidance and a template: [references/writing-the-brief.md](references/writing-the-brief.md).
|
|
65
|
+
|
|
66
|
+
### 2. Dispatch
|
|
67
|
+
|
|
68
|
+
Send the brief to Grok with the bundled helper. It wraps `grok -p`, captures the run, and writes a
|
|
69
|
+
structured `result.json` — so your only job is "run a command, read a file." (`<skill-dir>` below is
|
|
70
|
+
this skill's installed directory — the folder containing this `SKILL.md`, i.e. the directory you loaded
|
|
71
|
+
the skill from. Claude Code prints it as "Base directory for this skill" when the skill loads; on other
|
|
72
|
+
orchestrators use that same directory — if unsure where it landed, run
|
|
73
|
+
`find ~ -name relay.mjs -path '*grok-delegate*'` and substitute the directory above it.)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
node "<skill-dir>/scripts/relay.mjs" --brief brief.txt --cd /path/to/repo
|
|
77
|
+
# read-only (review/diagnosis; best-effort — verify touchedFiles): add --read-only
|
|
78
|
+
# continue the previous Grok session: add --resume-last (send only the delta brief)
|
|
79
|
+
# hard time limit (watchdog): add --timeout 2h (default: off; implementation runs routinely need 1-2h)
|
|
80
|
+
# see all options: node .../relay.mjs --help
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The helper defaults to a write-capable (`workspace-write`) autonomy profile — `--always-approve` plus
|
|
84
|
+
`--sandbox workspace` — and writes its artifacts to a temp dir, so the repo under review stays clean.
|
|
85
|
+
It **never commits** — see step 5. Mechanics, flags, and the `result.json` shape:
|
|
86
|
+
[references/dispatch-and-poll.md](references/dispatch-and-poll.md).
|
|
87
|
+
|
|
88
|
+
### 3. Wait for completion
|
|
89
|
+
|
|
90
|
+
The helper blocks until Grok finishes, so back it with whatever your orchestrator offers and resume
|
|
91
|
+
when it returns:
|
|
92
|
+
|
|
93
|
+
- **Claude Code:** run the Bash call with `run_in_background: true`; you are notified on completion.
|
|
94
|
+
- **Plain shell / other agents:** run it in the foreground for short tasks, or background it and poll
|
|
95
|
+
the result file — `… &` in bash/zsh (including Git Bash/WSL), or your shell's equivalent (`Start-Job`
|
|
96
|
+
in PowerShell, `start /b` in cmd). The run is done when `result.json` exists with a `status`. (A
|
|
97
|
+
pre-run usage error — bad args or an empty brief — instead exits with code 2 and a stderr message and
|
|
98
|
+
writes no result file, so check the exit code too. A missing `grok` binary exits 127 but *does* write
|
|
99
|
+
a `result.json` with status `grok_unavailable`.)
|
|
100
|
+
|
|
101
|
+
Do not trust progress trackers over reality: a run is finished when `result.json` is written and the
|
|
102
|
+
process has exited. Read the working tree, not a status line. The implementer's full report is
|
|
103
|
+
the `finalMessage` field in `result.json` (also printed in full on stdout between the report markers).
|
|
104
|
+
|
|
105
|
+
### 4. Review — do not trust the self-report
|
|
106
|
+
|
|
107
|
+
Grok's `result.json` includes its own summary and gate claims. **Re-verify, don't accept:**
|
|
108
|
+
|
|
109
|
+
- **Re-run the project's gates yourself** (the test/lint/build commands from step 1). Never take
|
|
110
|
+
"gates passed" on faith.
|
|
111
|
+
- **Read the diff** against the brief: did Grok do what was asked, nothing more (scope creep) and
|
|
112
|
+
nothing less? `touchedFiles` in the result is your starting point.
|
|
113
|
+
- **Run the relevant guard skills** on the diff if you have them installed (clean-code-guard,
|
|
114
|
+
test-guard, etc. from `guard-skills`) — this skill produces the work; those skills judge it.
|
|
115
|
+
- For schema/migration changes, round-trip them; for removals, grep for dangling references.
|
|
116
|
+
|
|
117
|
+
Full checklist: [references/review-and-land.md](references/review-and-land.md).
|
|
118
|
+
|
|
119
|
+
### 5. Land it
|
|
120
|
+
|
|
121
|
+
**The orchestrator commits.** Only after the gates pass and the diff holds:
|
|
122
|
+
|
|
123
|
+
- Commit the verified work yourself, with a clear message.
|
|
124
|
+
- If it needs changes, send a delta brief with `--resume-last` (don't restate the whole task) and
|
|
125
|
+
review again.
|
|
126
|
+
|
|
127
|
+
## Autonomy model
|
|
128
|
+
|
|
129
|
+
Grok's default permission mode is `ask`, which **blocks on approval prompts in a headless pipe**. The
|
|
130
|
+
relay therefore always sets autonomy explicitly:
|
|
131
|
+
|
|
132
|
+
| Relay flag | What Grok gets | Use when |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| *(default)* | `--always-approve --sandbox workspace` | Normal implementation — writes scoped to the working tree |
|
|
135
|
+
| `--read-only` | `--sandbox read-only --permission-mode plan` | Review / diagnosis — **best-effort, not enforced** (see caveat below) |
|
|
136
|
+
| `--full-access` | `--always-approve --sandbox off` | Explicit opt-in when the task needs unrestricted tools |
|
|
137
|
+
|
|
138
|
+
`--always-approve` alone would approve *all* tools (writes, shell, network) — closer to unrestricted
|
|
139
|
+
than to a workspace-scoped write. Pairing it with `--sandbox workspace` is what keeps the default
|
|
140
|
+
safe. Reach for `--full-access` only when the human asks for it.
|
|
141
|
+
|
|
142
|
+
**`--read-only` is best-effort, not a hard guarantee.** The read-only sandbox restricts out-of-workspace
|
|
143
|
+
filesystem/network access, not grok's own edit tool, and headless `plan` mode is advisory — a run
|
|
144
|
+
verified here still wrote the working tree when told to. Use `--read-only` to *signal* review intent,
|
|
145
|
+
but always confirm `touchedFiles` afterward; treat the diff, not the flag, as the guarantee. The relay
|
|
146
|
+
automates a reporting tripwire: it compares parsed git porcelain and fingerprints the working-tree
|
|
147
|
+
identity and index entries of Git-visible paths that were already dirty. `readOnlyViolation` is `true`
|
|
148
|
+
when either signal proves a change, `false` when coverage is complete and detects none, and `null` when
|
|
149
|
+
coverage is incomplete. Ignored paths, submodule internals, perfect restores, and attribution of
|
|
150
|
+
concurrent changes remain outside it, so the diff
|
|
151
|
+
review stays the guarantee.
|
|
152
|
+
|
|
153
|
+
## Authorization model
|
|
154
|
+
|
|
155
|
+
Delegation is something the human opts into. Once they have ("run this queue", "proceed"), committing
|
|
156
|
+
verified, gate-passing work is the agreed contract — that is the whole point. Two limits on that
|
|
157
|
+
mandate: **surface, don't absorb** (report Grok's design decisions, defensible-but-unasked turns, and
|
|
158
|
+
non-blocking nitpicks rather than silently keeping them) and **stop for scope changes** (if correct
|
|
159
|
+
completion needs going beyond the brief, ask — don't expand the mandate yourself). The full treatment
|
|
160
|
+
is in [references/review-and-land.md](references/review-and-land.md).
|
|
161
|
+
|
|
162
|
+
## References
|
|
163
|
+
|
|
164
|
+
- [references/writing-the-brief.md](references/writing-the-brief.md) — how to write a brief Grok can
|
|
165
|
+
execute blind: structure, XML blocks, the report contract, embedding the real gate commands.
|
|
166
|
+
- [references/dispatch-and-poll.md](references/dispatch-and-poll.md) — `relay.mjs` flags, the
|
|
167
|
+
`result.json` contract, backgrounding per orchestrator, and recovery when a run misbehaves.
|
|
168
|
+
- [references/review-and-land.md](references/review-and-land.md) — the review checklist, the commit
|
|
169
|
+
boundary, and the rework cycle via `--resume-last`.
|
|
170
|
+
- [references/multi-task-queues.md](references/multi-task-queues.md) — running a sequential queue:
|
|
171
|
+
carrying constraints forward, progress tracking, and the end-of-run coherence check.
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
## Limitations
|
|
175
|
+
|
|
176
|
+
- Docs-only import — executable `scripts/relay.mjs` not included; see upstream for full runtime. Requires `grok` CLI, Node 18+, git.
|
|
177
|
+
- Relay never commits — it only returns structured result JSON; you review and land the commit.
|
|
178
|
+
|
|
179
|
+
> Adapted from [amElnagdy/delegate-skills](https://github.com/amElnagdy/delegate-skills) (MIT) — docs-only, runtime not bundled.
|