{"record":{"id":"cd175dea61453db7","repo":"mihomo-party-org/clash-party","slug":"unreachable","errorCode":"unreachable","errorMessage":"err.message","messagePattern":"err\\.message","errorType":"exception","errorClass":"GatewayError","httpStatus":null,"severity":"error","filePath":"src/main/resolve/plugin/gateway.ts","lineNumber":88,"sourceCode":"  // Node 的 TLS/证书错误 code 形如 ERR_TLS_*, ERR_SSL_*, CERT_*, SELF_SIGNED_*, UNABLE_TO_*, DEPTH_ZERO_*\n  return /^(ERR_TLS|ERR_SSL|CERT_|SELF_SIGNED_|UNABLE_TO_|DEPTH_ZERO_)/.test(code)\n}\n\nasync function postJson(url: string, body: unknown, net: GatewayNet): Promise<RawResult> {\n  let res: { status: number; body: string }\n  try {\n    res = await requestOnce(url, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(body),\n      timeout: net.timeout,\n      maxBytes: MAX_BYTES,\n      lookup: lookupFor(net),\n      proxy: net.proxy\n    })\n  } catch (e) {\n    const err = e as NodeJS.ErrnoException\n    throw new GatewayError(isUnreachable(err) ? 'unreachable' : 'transient', err.message)\n  }\n  let json: Record<string, unknown> | undefined\n  try {\n    const parsed = JSON.parse(res.body)\n    json =\n      typeof parsed === 'object' && parsed !== null\n        ? (parsed as Record<string, unknown>)\n        : undefined\n  } catch {\n    json = undefined\n  }\n  return { status: res.status, json, text: res.body }\n}\n\nfunction classify(r: RawResult): GatewayError | null {\n  if (r.status === 410 || r.json?.error === 'gateway_retired') {\n    return new GatewayError('retired', 'gateway retired', r.status)\n  }","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/mihomo-party-org/clash-party/blob/911e090537acdf7c50bee1c3aebecc2ef119a8b5/src/main/resolve/plugin/gateway.ts#L70-L106","documentation":"postJson() wraps any failure from the underlying HTTP client into a GatewayError. The category is chosen by isUnreachable(err): DNS/connect failures become 'unreachable', everything else becomes 'transient', and err.message becomes the GatewayError message. So this is a network-layer failure during a gateway POST, not a gateway application error.","triggerScenarios":"The fetch/post to the gateway throws — DNS resolution failure, TCP connect refused/timeout, TLS error, proxy misconfiguration, or request-construction failure inside the HTTP client.","commonSituations":"Gateway host typo'd or offline, no internet/VPN, DNS blocked or intercepted, corporate proxy set via net.proxy unreachable, or firewall dropping the connection.","solutions":["Check network connectivity and that the gateway host is reachable (ping/curl the gateway URL).","Verify the gateway hostname and port in your GatewayTarget config.","Check proxy settings — a wrong net.proxy value causes unreachable errors.","Classified 'unreachable': retry with backoff once connectivity is restored; this is not an app-level retry target."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight reachability check before POSTing\nconst u = new URL(target.gateway)\nawait new Promise((ok, bad) => {\n  const s = require('node:net').connect(Number(u.port) || 443, u.hostname, ok)\n  s.on('error', bad); s.setTimeout(5000, () => { s.destroy(); bad(new Error('timeout')) })\n})","typeGuard":null,"tryCatchPattern":"try {\n  return await postJson(target, ep, body, dev, net)\n} catch (e) {\n  if (e instanceof GatewayError && e.code === 'unreachable') {\n    // exponential backoff, bounded retries; surface connectivity guidance\n  } else throw e\n}","preventionTips":["Verify gateway hostname/port and DNS resolution before deployment.","Test proxy settings (net.proxy) — wrong proxies surface as unreachable.","Monitor connectivity and alert on 'unreachable' GatewayErrors rather than retrying forever."],"tags":["network","gateway","unreachable","http"],"backgroundTag":"network-unreachable","analyzedSha":"911e090537acdf7c50bee1c3aebecc2ef119a8b5","analyzedAt":"2026-08-30T13:00:49.174Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}