koala73/worldmonitor · error · SafeWebMcpError
Dashboard tab result exceeded the safe output limit.
Error message
Dashboard tab result exceeded the safe output limit.
What it means
boundDashboardTabMutation sanitizes a tab mutation result (create/rename/delete/select), coercing counts, capping lockReason to 32 chars, and then asserts the serialized result fits MAX_OUTPUT_CHARS. If it still exceeds the budget it throws this SafeWebMcpError. Mutation results are tiny by design, so hitting this indicates the result structurally ballooned beyond what the bounder can trim.
Solutions
- Retry the mutation once; this is not expected in normal operation and may be transient state.
- Check whether you are on a dashboard version that added fields to DashboardTabMutationResult; upgrade or downgrade to a matched pair of tool/app versions.
- Shorten the tab name/id you are mutating to rule out oversize user input.
- Report a bug with the tool name and args — the bounder should always produce a sub-budget mutation result.
Example fix
// before
await tools.call('rename_dashboard_tab', { id, name: 'x'.repeat(5000) });
// after
await tools.call('rename_dashboard_tab', { id, name: name.slice(0, 64) }); Defensive patterns
Strategy: try-catch
Validate before calling
function validTabMutationArgs(args) {
return typeof args.id === 'string' && args.id.length <= 128
&& (args.name === undefined || (typeof args.name === 'string' && args.name.length <= 64));
} Try / catch
try {
return await tools.call('rename_dashboard_tab', { id, name });
} catch (e) {
if (e.message.includes('tab result exceeded')) {
// unexpected: mutation results are tiny; log and retry once, then report
console.error('oversized tab mutation result', { id, name: name.length });
return tools.call('rename_dashboard_tab', { id, name: name.slice(0, 32) });
}
throw e;
} Prevention
- Bound user-supplied tab names to a sane length (<= 64 chars) before calling.
- Keep tab ids short and generated, not free-form user text.
- Keep tool and dashboard versions matched.
- Treat occurrences as a bug-report condition with args captured.
When it happens
Trigger: A create_dashboard_tab / rename_dashboard_tab / delete_dashboard_tab / select_dashboard_tab call whose bounded result object still serializes above MAX_OUTPUT_CHARS — practically only when the result carries oversized fields the bounder does not truncate.
Common situations: A dashboard version returning extra unbounded fields in the mutation result; extremely long tab ids or lockReason variants; a regression where the bounder's field whitelist stopped covering a new large field.
Related errors
- Dashboard tab list exceeded the safe output limit.
- Dashboard navigation result exceeded the safe output limit.
- Dashboard panel catalog exceeded the safe output limit.
- app_destroyed
- app_destroyed
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-01).
Data as JSON: /api/errors/be3ec1ba3523f81d.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/webmcp.ts:1560
...(typeof result.effectiveCollapsed === 'boolean'
? { effectiveCollapsed: result.effectiveCollapsed }
: {}),
...(typeof result.requestedFullscreen === 'boolean'
? { requestedFullscreen: result.requestedFullscreen }
: {}),
...(typeof result.effectiveFullscreen === 'boolean'
? { effectiveFullscreen: result.effectiveFullscreen }
: {}),
...(ok ? { changed: result.changed === true } : { changed: false }),
...(result.unchanged === true ? { unchanged: true } : {}),
...(typeof result.persisted === 'boolean' ? { persisted: result.persisted } : {}),
...(!ok && reason ? { reason } : {}),
message: boundedText(result.message, 160) || (ok ? 'Panel layout updated.' : 'Panel layout change denied.'),
};
}
function boundDashboardTabList(
snapshot: DashboardTabListSnapshot,
cursor?: string,
): DashboardTabActionResult {
const sourceTabs = Array.isArray(snapshot.tabs) ? snapshot.tabs : [];
const tabs = sourceTabs.map((tab) => ({
id: boundedText(tab?.id, 64),
name: boundedText(tab?.name, DASHBOARD_TAB_NAME_MAX_LENGTH),
active: tab?.active === true,
canDelete: tab?.canDelete === true,
})).filter((tab) => tab.id);
const tabCount = Math.max(
Math.max(0, Math.floor(boundedNumber(snapshot.tabCount)) || tabs.length),
tabs.length,
);
const activeTabId = boundedText(snapshot.activeTabId, 64);
const canCreate = snapshot.canCreate === true;
const cap = snapshot.cap === null || typeof snapshot.cap === 'number' ? snapshot.cap : null;
const createBlockReason = snapshot.createBlockReason
? boundedText(snapshot.createBlockReason, 32) as DashboardTabListSnapshot['createBlockReason']View on GitHub (pinned to e586b8b4b8)