mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:30:43 +00:00
fix(gateway): surface unreachable status diagnostics
This commit is contained in:
@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Status: show the `openai-codex` OAuth profile for `openai/gpt-*` sessions running through the native Codex runtime instead of reporting auth as unknown. (#76197) Thanks @mbelinky.
|
||||
- Plugins/externalization: keep diagnostics ClawHub packages and persisted bundled-plugin relocation on npm-first install metadata for launch, and omit Discord from the core package now that its external package is published. Thanks @vincentkoc.
|
||||
- Plugins/Codex: allow the official npm Codex plugin to install without the unsafe-install override, keep `/codex` command ownership, and cover the real npm Docker live path through managed `.openclaw/npm` dependencies plus uninstall failure proof.
|
||||
- Gateway/status: add concrete service, config, listener-owner, and log collection next steps when gateway probes fail and Bonjour finds no local gateway, so frozen or port-conflict reports include the data needed for root-cause triage. Refs #49012. Thanks @vincentkoc.
|
||||
|
||||
## 2026.5.2
|
||||
|
||||
|
||||
@@ -312,6 +312,46 @@ describe("gateway-status command", () => {
|
||||
expect(targets[0]?.summary).toBeTruthy();
|
||||
});
|
||||
|
||||
it("includes diagnostic next steps when no gateway is reachable or discoverable", async () => {
|
||||
const { runtime, runtimeLogs, runtimeErrors } = createRuntimeCapture();
|
||||
const defaultProbeGateway = probeGateway.getMockImplementation();
|
||||
try {
|
||||
probeGateway.mockImplementation(async (opts: { url: string }) => ({
|
||||
ok: false,
|
||||
url: opts.url,
|
||||
connectLatencyMs: null,
|
||||
error: "connection refused",
|
||||
close: null,
|
||||
auth: {
|
||||
role: null,
|
||||
scopes: [],
|
||||
capability: "unknown",
|
||||
},
|
||||
health: null,
|
||||
status: null,
|
||||
presence: null,
|
||||
configSnapshot: null,
|
||||
}));
|
||||
|
||||
await expect(runGatewayStatus(runtime, { timeout: "1000", json: true })).rejects.toThrow(
|
||||
"__exit__:1",
|
||||
);
|
||||
} finally {
|
||||
probeGateway.mockReset();
|
||||
if (defaultProbeGateway) {
|
||||
probeGateway.mockImplementation(defaultProbeGateway);
|
||||
}
|
||||
}
|
||||
|
||||
expect(runtimeErrors).toHaveLength(0);
|
||||
const parsed = JSON.parse(runtimeLogs.join("\n")) as {
|
||||
warnings?: Array<{ code?: string; message?: string }>;
|
||||
};
|
||||
const warning = parsed.warnings?.find((entry) => entry.code === "no_gateway_reachable");
|
||||
expect(warning?.message).toContain("openclaw gateway status --deep --require-rpc");
|
||||
expect(warning?.message).toContain("ss -ltnp");
|
||||
});
|
||||
|
||||
it("omits discovery wsUrl when only TXT hints are present", async () => {
|
||||
const { runtime, runtimeLogs, runtimeErrors } = createRuntimeCapture();
|
||||
discoverGatewayBeacons.mockResolvedValueOnce([
|
||||
|
||||
@@ -121,6 +121,7 @@ export async function gatewayStatusCommand(
|
||||
sshTarget: probePass.sshTarget,
|
||||
sshTunnelStarted: probePass.sshTunnelStarted,
|
||||
sshTunnelError: probePass.sshTunnelError,
|
||||
discoveryCount: probePass.discovery.length,
|
||||
localTlsLoadError:
|
||||
localTlsRuntime && !localTlsRuntime.enabled && localTlsRuntime.required
|
||||
? (localTlsRuntime.error ?? "gateway tls is enabled but local TLS runtime could not load")
|
||||
|
||||
@@ -18,7 +18,8 @@ vi.mock("../../terminal/theme.js", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
const { writeGatewayStatusJson, writeGatewayStatusText } = await import("./output.js");
|
||||
const { buildGatewayStatusWarnings, writeGatewayStatusJson, writeGatewayStatusText } =
|
||||
await import("./output.js");
|
||||
|
||||
function createRuntimeCapture(): RuntimeEnv {
|
||||
return {
|
||||
@@ -80,6 +81,34 @@ describe("gateway status output", () => {
|
||||
writeRuntimeJson.mockReset();
|
||||
});
|
||||
|
||||
it("warns with diagnostic next steps when no probes or Bonjour discovery find a gateway", () => {
|
||||
const warnings = buildGatewayStatusWarnings({
|
||||
probed: [
|
||||
createTarget(
|
||||
"localLoopback",
|
||||
createProbe("unknown", {
|
||||
ok: false,
|
||||
connectLatencyMs: null,
|
||||
error: "connection refused",
|
||||
}),
|
||||
),
|
||||
],
|
||||
sshTarget: null,
|
||||
sshTunnelStarted: false,
|
||||
sshTunnelError: null,
|
||||
discoveryCount: 0,
|
||||
});
|
||||
|
||||
expect(warnings).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: "no_gateway_reachable",
|
||||
message: expect.stringContaining("openclaw gateway status --deep --require-rpc"),
|
||||
targetIds: ["localLoopback"],
|
||||
}),
|
||||
);
|
||||
expect(warnings.at(0)?.message).toContain("lsof -nP -iTCP:<port>");
|
||||
});
|
||||
|
||||
it("derives summary capability from reachable probes only in json output", () => {
|
||||
const runtime = createRuntimeCapture();
|
||||
writeGatewayStatusJson({
|
||||
|
||||
@@ -18,6 +18,9 @@ export type GatewayStatusWarning = {
|
||||
targetIds?: string[];
|
||||
};
|
||||
|
||||
const noReachableGatewayDiagnostic =
|
||||
"No gateway answered any probe and Bonjour discovery returned no local gateways. Run `openclaw gateway status --deep --require-rpc` to inspect service state, config paths, listener owners, and logs; include `ss -ltnp` or `lsof -nP -iTCP:<port> -sTCP:LISTEN` for the configured port when filing a report.";
|
||||
|
||||
export function pickPrimaryProbedTarget(probed: GatewayStatusProbedTarget[]) {
|
||||
const reachable = probed.filter((entry) => isProbeReachable(entry.probe));
|
||||
return (
|
||||
@@ -35,6 +38,7 @@ export function buildGatewayStatusWarnings(params: {
|
||||
sshTunnelStarted: boolean;
|
||||
sshTunnelError: string | null;
|
||||
localTlsLoadError?: string | null;
|
||||
discoveryCount?: number;
|
||||
}): GatewayStatusWarning[] {
|
||||
const reachable = params.probed.filter((entry) => isProbeReachable(entry.probe));
|
||||
const degradedScopeLimited = params.probed.filter((entry) =>
|
||||
@@ -59,6 +63,13 @@ export function buildGatewayStatusWarnings(params: {
|
||||
targetIds: ["localLoopback"],
|
||||
});
|
||||
}
|
||||
if (reachable.length === 0 && params.discoveryCount === 0) {
|
||||
warnings.push({
|
||||
code: "no_gateway_reachable",
|
||||
message: noReachableGatewayDiagnostic,
|
||||
targetIds: params.probed.map((entry) => entry.target.id),
|
||||
});
|
||||
}
|
||||
if (reachable.length > 1) {
|
||||
warnings.push({
|
||||
code: "multiple_gateways",
|
||||
|
||||
Reference in New Issue
Block a user