gethomepage/homepage · error · Error
Failed parsing '${action}' response
Error message
Failed parsing '${action}' response What it means
Thrown when httpProxy returned 200 for a Fritz!Box SOAP action but the response body could not be parsed: either JSON.parse(xml2json(data)) threw, or the deeply-nested element path (elements[0].elements[0].elements[0].elements) was not the expected shape. The router answered, but not in the SOAP envelope form the parser assumes.
Source
Thrown at src/widgets/fritzbox/proxy.js:42
"</s:Body>" +
"</s:Envelope>",
};
const apiUrl = `${apiBaseUrl}/igdupnp/control/${servicePath}`;
const [status, , data] = await httpProxy(apiUrl, params);
if (status !== 200) {
logger.debug(`HTTP ${status} performing SoapRequest for ${service}->${action}`, data);
throw new Error(`Failed fetching '${action}'`);
}
const response = {};
try {
const jsonData = JSON.parse(xml2json(data));
const responseElements = jsonData?.elements?.[0]?.elements?.[0]?.elements?.[0]?.elements || [];
responseElements.forEach((element) => {
response[element.name] = element.elements?.[0].text || "";
});
} catch (e) {
logger.debug(`Failed parsing ${service}->${action} response:`, data);
throw new Error(`Failed parsing '${action}' response`);
}
return response;
}
export default async function fritzboxProxyHandler(req, res) {
const { group, service, index } = req.query;
const serviceWidget = await getServiceWidget(group, service, index);
if (!serviceWidget) {
res.status(500).json({ error: { message: "Service widget not found" } });
return;
}
if (!serviceWidget.url) {
res.status(500).json({ error: { message: "Service widget url not configured" } });
return;
}View on GitHub (pinned to b6dca1ae03)
Solutions
- Inspect the debug log line 'Failed parsing ${service}->${action} response:' which dumps the raw data just before the throw.
- Confirm the action/service actually permits this call for the current Fritz!Box user; an unauthorized SOAP call often returns a fault envelope rather than a real payload.
- If a firmware update changed the envelope, adjust the element path parsing or upgrade Homepage to a version that matches the firmware.
- Remove any reverse proxy in front of the Fritz!Box that may transform/truncate the SOAP body.
- Verify the request is hitting /igdupnp/control/... and not a redirect that returns HTML.
Example fix
// before
const jsonData = JSON.parse(xml2json(data));
// after
let jsonData;
try {
jsonData = JSON.parse(xml2json(data));
} catch (e) {
throw new Error(`Failed to convert Fritz!Box response to JSON for ${action}: ${e.message}; raw=${Buffer.from(data).toString().slice(0, 200)}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Detect a SOAP fault early so it is not misread as a parse failure.
function looksLikeSoapFault(raw) {
const s = Buffer.from(raw).toString();
return /<fault>|<faultcode>|UPnPError/i.test(s);
} Type guard
function isFritzboxSoapResponse(json) {
const node = json?.elements?.[0]?.elements?.[0]?.elements?.[0]?.elements;
return Array.isArray(node) && node.length > 0;
} Try / catch
try {
const jsonData = JSON.parse(xml2json(data));
if (!isFritzboxSoapResponse(jsonData)) {
throw new Error(`Unexpected Fritz!Box SOAP envelope shape for ${action}`);
}
// ...build response
} catch (e) {
if (looksLikeSoapFault(data)) {
res.status(403).json({ error: `Fritz!Box denied ${action} (SOAP fault)` });
} else {
res.status(502).json({ error: `Could not parse Fritz!Box ${action} response` });
}
} Prevention
- Log the raw body once at debug level whenever parsing fails so the cause is visible.
- Distinguish SOAP faults from malformed envelopes; they need different fixes (permissions vs firmware).
- Pin Homepage and Fritz!OS versions together in staging to catch envelope-shape regressions.
When it happens
Trigger: The router returned a SOAP fault envelope (still 200) whose structure differs from a normal response, an HTML login/error page with a 200 status, a truncated response from a flaky proxy, or a firmware change that alters the envelope nesting.
Common situations: Fritz!Box returning a fault/permission-denied page with HTTP 200; an intermediary reverse proxy rewriting the body; partial response due to connection drop; a service whose response legitimately omits one nesting level (so the path resolves to undefined).
Related errors
- Failed fetching '${action}'
- Missing Glances URL
- Authorization failure getting data from glances API. Data: $
- HTTP ${status} getting data from glances API. Data: ${data.t
- Error getting data from Audiobookshelf: ${status}. Data: ${d
AI-assisted analysis of gethomepage/homepage@b6dca1ae03 (2026-08-13).
Data as JSON: /api/errors/db0dca1e5303eec0.
Report an issue: GitHub.