mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
fix(onboard): redact gateway health failures (#127071)
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
committed by
GitHub
parent
8ac7dd254c
commit
76af07b735
@@ -62,8 +62,11 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
|
||||
envSnapshot.restore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
gatewayReachableState.mock = undefined;
|
||||
const { resetSecretRedactionRegistryForTest } =
|
||||
await import("../logging/secret-redaction-registry.test-support.js");
|
||||
resetSecretRedactionRegistryForTest();
|
||||
testConfigStore.clear();
|
||||
capturedReplaceConfigFileCalls.length = 0;
|
||||
configWritePluginLeaseDepths.length = 0;
|
||||
@@ -609,10 +612,17 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
|
||||
|
||||
it("emits structured JSON diagnostics when daemon health fails", async () => {
|
||||
await withStateDir("state-local-daemon-health-json-fail-", async (stateDir) => {
|
||||
const registeredSecret = "qa-onboarding-health-secret";
|
||||
const { registerSecretValueForRedaction } =
|
||||
await import("../logging/secret-redaction-registry.js");
|
||||
registerSecretValueForRedaction(registeredSecret);
|
||||
gatewayReachableState.mock = vi.fn(async () => ({
|
||||
ok: false,
|
||||
detail: "gateway closed (1006 abnormal closure (no close frame)): no close reason",
|
||||
detail: `gateway closed (1006 abnormal closure (no close frame)): ${registeredSecret}`,
|
||||
}));
|
||||
readLastGatewayErrorLineMock.mockResolvedValueOnce(
|
||||
`Gateway failed to start: required secrets are unavailable: ${registeredSecret}`,
|
||||
);
|
||||
|
||||
const { runtimeWithCapture, readCapturedJson } = createOnboardJsonCaptureRuntime();
|
||||
await expectOnboardLocalJsonSetupFailure({
|
||||
@@ -651,6 +661,7 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
|
||||
expect(parsed.diagnostics?.service?.runtimeStatus).toBe("running");
|
||||
expect(parsed.diagnostics?.service?.pid).toBe(4242);
|
||||
expect(parsed.diagnostics?.lastGatewayError).toContain("required secrets are unavailable");
|
||||
expect(readCapturedJson()).not.toContain(registeredSecret);
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
@@ -694,12 +705,22 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
|
||||
|
||||
it("routes thrown health-check errors through the onboarding failure owner", async () => {
|
||||
await withStateDir("state-local-health-failure-text-", async (stateDir) => {
|
||||
const registeredSecret = "qa-onboarding-health-secret";
|
||||
const { registerSecretValueForRedaction } =
|
||||
await import("../logging/secret-redaction-registry.js");
|
||||
registerSecretValueForRedaction(registeredSecret);
|
||||
gatewayReachableState.mock = vi.fn(async () => ({ ok: true }));
|
||||
healthCommandMock.mockRejectedValueOnce(new Error("health request timed out"));
|
||||
healthCommandMock.mockRejectedValueOnce(
|
||||
new Error(`health request timed out: ${registeredSecret}`),
|
||||
);
|
||||
|
||||
await expect(
|
||||
runNonInteractiveSetup(createOnboardLocalDaemonOptions(stateDir), runtime),
|
||||
).rejects.toThrow(/health check failed[\s\S]*health request timed out/);
|
||||
const failure = await runNonInteractiveSetup(
|
||||
createOnboardLocalDaemonOptions(stateDir),
|
||||
runtime,
|
||||
).catch((error: unknown) => error);
|
||||
expect(failure).toBeInstanceOf(Error);
|
||||
expect(String(failure)).toMatch(/health check failed[\s\S]*health request timed out/);
|
||||
expect(String(failure)).not.toContain(registeredSecret);
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* are kept here so local and remote setup report failures consistently.
|
||||
*/
|
||||
import type { GatewayServiceLoadState } from "../../../daemon/service-types.js";
|
||||
import { redactSecrets } from "../../../logging/redact.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../../runtime.js";
|
||||
import type { OnboardOptions } from "../../onboard-types.js";
|
||||
|
||||
@@ -197,8 +198,17 @@ export function logNonInteractiveOnboardingFailure(params: {
|
||||
});
|
||||
const recoveryHint = recoveryHintForGatewayHealthFailure(classification);
|
||||
const hints = [...(recoveryHint ? [recoveryHint] : []), ...(params.hints?.filter(Boolean) ?? [])];
|
||||
const gatewayRuntime = formatGatewayRuntimeSummary(params.diagnostics);
|
||||
const service = params.diagnostics?.service;
|
||||
const output = redactSecrets({
|
||||
message: params.message,
|
||||
detail: params.detail,
|
||||
hints,
|
||||
gateway: params.gateway,
|
||||
daemonInstall: params.daemonInstall,
|
||||
daemonRuntime: params.daemonRuntime,
|
||||
diagnostics: params.diagnostics,
|
||||
});
|
||||
const gatewayRuntime = formatGatewayRuntimeSummary(output.diagnostics);
|
||||
const service = output.diagnostics?.service;
|
||||
const serviceLoadText = service
|
||||
? service.loadState.status === "loaded"
|
||||
? service.loadedText
|
||||
@@ -210,32 +220,32 @@ export function logNonInteractiveOnboardingFailure(params: {
|
||||
ok: false,
|
||||
mode: params.mode,
|
||||
phase: params.phase,
|
||||
message: params.message,
|
||||
message: output.message,
|
||||
classification,
|
||||
detail: params.detail,
|
||||
gateway: params.gateway,
|
||||
detail: output.detail,
|
||||
gateway: output.gateway,
|
||||
installDaemon: Boolean(params.installDaemon),
|
||||
daemonInstall: params.daemonInstall,
|
||||
daemonRuntime: params.daemonRuntime,
|
||||
diagnostics: params.diagnostics,
|
||||
hints: hints.length > 0 ? hints : undefined,
|
||||
daemonInstall: output.daemonInstall,
|
||||
daemonRuntime: output.daemonRuntime,
|
||||
diagnostics: output.diagnostics,
|
||||
hints: output.hints.length > 0 ? output.hints : undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = [
|
||||
params.message,
|
||||
output.message,
|
||||
classification ? `Classification: ${classification}` : undefined,
|
||||
params.detail ? `Last probe: ${params.detail}` : undefined,
|
||||
output.detail ? `Last probe: ${output.detail}` : undefined,
|
||||
service ? `Service: ${service.label} (${serviceLoadText})` : undefined,
|
||||
gatewayRuntime ? `Runtime: ${gatewayRuntime}` : undefined,
|
||||
params.diagnostics?.lastGatewayError
|
||||
? `Last gateway error: ${params.diagnostics.lastGatewayError}`
|
||||
output.diagnostics?.lastGatewayError
|
||||
? `Last gateway error: ${output.diagnostics.lastGatewayError}`
|
||||
: undefined,
|
||||
params.diagnostics?.inspectError
|
||||
? `Diagnostics warning: ${params.diagnostics.inspectError}`
|
||||
output.diagnostics?.inspectError
|
||||
? `Diagnostics warning: ${output.diagnostics.inspectError}`
|
||||
: undefined,
|
||||
hints.length > 0 ? hints.join("\n") : undefined,
|
||||
output.hints.length > 0 ? output.hints.join("\n") : undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
|
||||
Reference in New Issue
Block a user