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 {
|
||||
// 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)`;
|
||||
if (process.env.DEBUG) {
|
||||
console.error("Docmost request failed; no response:", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
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({
|
||||
method: "post",
|
||||
url: "/comments/create",
|
||||
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);
|
||||
assert.equal(
|
||||
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", () => {
|
||||
|
||||
Reference in New Issue
Block a user