fix(mcp): no-response диагностика — не отдавать сырой error.message (утечка host в модель) (внутр. ревью #437)
no-response ветка формата использовала error.code ?? error.message: при
отсутствии code axios-сообщения сетевых ошибок содержат host:port
('connect ECONNREFUSED 127.0.0.1:3000', 'getaddrinfo ENOTFOUND host'), что
нарушает инвариант #437 «host никогда не попадает в видимое модели сообщение».
Теперь только error.code (?? 'network error'); полный нативный текст уходит в
stderr под DEBUG. code проставлен фактически для всех реальных no-response
ошибок. Тест обновлён: сырое host-содержащее сообщение -> нейтральный reason,
плюс ассерт что host в сообщении отсутствует.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -306,8 +306,17 @@ export function formatDocmostAxiosError(error: any): void {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No response at all (ECONNREFUSED / ETIMEDOUT / ECONNRESET / DNS / timeout).
|
// No response at all (ECONNREFUSED / ETIMEDOUT / ECONNRESET / DNS / timeout).
|
||||||
const reason = error.code ?? error.message ?? "network error";
|
// Use ONLY error.code, never the raw error.message: axios network messages
|
||||||
|
// embed host:port ("connect ECONNREFUSED 127.0.0.1:3000", "getaddrinfo
|
||||||
|
// ENOTFOUND host") and #437's invariant is that the host never reaches the
|
||||||
|
// model-visible message. code is set for essentially every real no-response
|
||||||
|
// error (ECONNREFUSED/ETIMEDOUT/ECONNRESET/ENOTFOUND/ECONNABORTED); the full
|
||||||
|
// native message still goes to stderr under DEBUG.
|
||||||
|
const reason = error.code ?? "network error";
|
||||||
message = `${methodPath} failed: ${reason} (no response from server)`;
|
message = `${methodPath} failed: ${reason} (no response from server)`;
|
||||||
|
if (process.env.DEBUG) {
|
||||||
|
console.error("Docmost request failed; no response:", error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.length > ERROR_MESSAGE_CAP) {
|
if (message.length > ERROR_MESSAGE_CAP) {
|
||||||
|
|||||||
@@ -180,18 +180,22 @@ test("no response uses error.code + path + 'no response from server'", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("no response falls back to error.message when code is absent", () => {
|
test("no response with no code falls back to a neutral reason (raw message not leaked — it may embed host:port)", () => {
|
||||||
const err = makeAxiosError({
|
const err = makeAxiosError({
|
||||||
method: "post",
|
method: "post",
|
||||||
url: "/comments/create",
|
url: "/comments/create",
|
||||||
status: undefined,
|
status: undefined,
|
||||||
message: "timeout of 30000ms exceeded",
|
// A raw axios network message like "connect ECONNREFUSED 127.0.0.1:3000"
|
||||||
|
// embeds the host; #437's invariant is that it never reaches the message.
|
||||||
|
message: "connect ECONNREFUSED 10.0.0.5:3000",
|
||||||
});
|
});
|
||||||
formatDocmostAxiosError(err);
|
formatDocmostAxiosError(err);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
err.message,
|
err.message,
|
||||||
"POST /comments/create failed: timeout of 30000ms exceeded (no response from server)",
|
"POST /comments/create failed: network error (no response from server)",
|
||||||
);
|
);
|
||||||
|
// And the host must NOT appear anywhere in the model-visible message.
|
||||||
|
assert.ok(!err.message.includes("10.0.0.5"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("path drops the host and the query string", () => {
|
test("path drops the host and the query string", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user