fix(gateway): clarify skip-deferral reply drain (#128178)

Co-authored-by: gaoanze <gaoanze@meituan.com>
This commit is contained in:
gaoanze888
2026-08-23 23:40:17 +08:00
committed by GitHub
parent ddd12ca27b
commit 67613e73c5
5 changed files with 80 additions and 4 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ openclaw daemon uninstall
- `status`: shows service install state (launchd/systemd/schtasks) and probes Gateway health.
- `install`: installs the service; `--force` reinstalls/overwrites an existing install.
- `restart --safe`: asks the running Gateway to preflight active work and schedule one coalesced restart after work drains, bounded to 5 minutes. When that budget expires, the restart is forced anyway. Plain `restart` uses the service manager directly; `--force` is the immediate override.
- `restart --safe --skip-deferral`: bypasses the active-work deferral gate so the Gateway restarts immediately even when blockers are reported. Requires `--safe`.
- `restart --safe --skip-deferral`: bypasses only the active-work deferral gate. Shutdown may still wait for pending replies to drain before the Gateway process exits. Requires `--safe`.
## Notes
+1 -1
View File
@@ -131,7 +131,7 @@ openclaw gateway restart --wait 30s
`--safe` asks the running Gateway to preflight active work and schedule one coalesced restart after that work drains. The wait is bounded to 5 minutes; when the budget expires the restart is forced. `--safe` cannot combine with `--force` or `--wait`.
`--skip-deferral` bypasses the active-work deferral gate on a safe restart, so the Gateway restarts immediately even with reported blockers. It requires `--safe` — use it when a deferral is stuck on a runaway task.
`--skip-deferral` bypasses only the safe-restart active-work deferral gate. It can move the Gateway into shutdown even while active-work blockers are reported, but the close-stage pending-reply drain still applies before the process exits. It requires `--safe` — use it when a deferral is stuck on a runaway task and reply delivery can still be allowed to settle.
`--wait <duration>` overrides the drain budget for a plain (non-safe) restart. Accepts bare milliseconds or unit suffixes `ms`, `s`, `m`, `h`, `d` (e.g. `30s`, `5m`, `1h30m`); `--wait 0` waits indefinitely. Not compatible with `--force` or `--safe`.
@@ -0,0 +1,71 @@
// Safe gateway restart tests cover operator-facing acknowledgement copy.
import { beforeEach, describe, expect, it, vi } from "vitest";
const callGatewayCli = vi.hoisted(() => vi.fn());
const appendGatewayLifecycleAudit = vi.hoisted(() => vi.fn());
const runtimeLog = vi.hoisted(() => vi.fn());
const runtimeWriteJson = vi.hoisted(() => vi.fn());
vi.mock("../../gateway/call.js", () => ({
callGatewayCli,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: {
log: runtimeLog,
writeJson: runtimeWriteJson,
},
writeRuntimeJson: (_runtime: unknown, payload: unknown) => runtimeWriteJson(payload),
}));
vi.mock("./lifecycle-audit.js", () => ({
appendGatewayLifecycleAudit,
}));
describe("runSafeGatewayRestart", () => {
beforeEach(() => {
callGatewayCli.mockReset();
appendGatewayLifecycleAudit.mockReset();
runtimeLog.mockReset();
runtimeWriteJson.mockReset();
});
it("reports that skip-deferral still allows close-stage reply drain", async () => {
const { runSafeGatewayRestart } = await import("./lifecycle-safe-restart.js");
callGatewayCli.mockResolvedValueOnce({
status: "scheduled",
preflight: {
safe: false,
activeWork: {
queueSize: 1,
runningTasks: 0,
activeRequests: 0,
activeAgentRuns: 0,
pendingReplies: 2,
totalActive: 3,
},
blockers: [{ kind: "pending-replies", count: 2, message: "2 pending reply(ies)" }],
summary: "restart deferred: 2 pending reply(ies)",
},
restart: { pid: 123 },
});
await expect(
runSafeGatewayRestart({ json: true, safe: true, skipDeferral: true }),
).resolves.toBe(true);
expect(callGatewayCli).toHaveBeenCalledWith({
method: "gateway.restart.request",
params: { reason: "gateway.restart.safe", skipDeferral: true },
timeoutMs: 10_000,
});
expect(runtimeWriteJson).toHaveBeenCalledWith(
expect.objectContaining({
message:
"safe restart requested; gateway bypassing active-work deferral; " +
"shutdown may still wait for pending replies to drain",
result: "scheduled",
}),
);
});
});
+2 -1
View File
@@ -83,7 +83,8 @@ export async function runSafeGatewayRestart(
? "safe restart requested; gateway will restart after active work drains " +
"(bounded wait; may force after the timeout expires)"
: skipDeferral
? "safe restart requested; gateway bypassing active-work deferral"
? "safe restart requested; gateway bypassing active-work deferral; " +
"shutdown may still wait for pending replies to drain"
: "safe restart requested; gateway will restart momentarily";
const payload = {
ok: true,
@@ -153,7 +153,11 @@ export function addGatewayServiceCommands(parent: Command, opts?: { statusDescri
"(bounded wait; may force after the timeout expires)",
false,
)
.option("--skip-deferral", "Bypass the safe-restart deferral gate; requires --safe", false)
.option(
"--skip-deferral",
"Bypass the safe-restart active-work deferral gate; close-stage reply drain still applies; requires --safe",
false,
)
.option(
"--wait <duration>",
"Wait duration before restart (ms, 10s, 5m; 0 waits indefinitely). " +