Thanks! Will test later / tomorow!
Also claude was struggling with finding broken flows (deleted device cards for example).
This is the report.
Sorry for all the reports. I really appreciate the updates! I am using it a lot to “spring clean” my flows and automations right now!
Homey MCP — broken-flow detection returns false negatives on every affected flow
Date: 2026-08-08
Component: Homey MCP server — flow integrity tools
Severity: High (silent failure in a diagnostic tool)
Summary
flows_get_broken is documented as listing flows that are broken “when a card it uses no longer
exists, for example after a device or app was removed”. In practice it appears to pass through
Homey’s own flow.broken property rather than resolving card references itself. That property was
false for every affected flow on this installation — including a flow constructed specifically to
be broken — so the tool returns an empty list and the user is told everything is fine.
This is a silent failure in a diagnostic tool, which is the worst place for one: a clean result is
indistinguishable from a working check. The same root cause affects two other surfaces.
| Metric |
Value |
| Flows scanned |
99 |
| Real problems found by manual cross-reference |
4 |
| Reported by the MCP |
0 |
| Detection rate |
0% |
The fix is cheap. getFlowCardTriggers(), getFlowCardConditions() and
getFlowCardActions() already return the fully-resolved live card registry — 2,559 manager/app
cards plus 3,157 device cards on this Homey. Validating a flow is a set membership test per card.
No new API surface is required.
Environment
|
|
| Homey |
Homey Pro |
| Devices |
148 |
| Flows |
26 basic + 73 advanced = 99 |
| Cross-check |
Flow Checker 1.37.2 (Martijn Poppen) |
Reproduction
- Create an Advanced Flow using one device for a trigger, a condition and an action.
- Delete that device from Homey.
- Call
flows_get_broken.
Expected: the flow is listed, ideally with the three offending cards.
Actual: {"count": 0, "broken": []}
The reference flow used here is Test Broken flow cards
(4a537833-b40e-4727-b5f9-8b7a1f392bc1). All three of its cards reference the deleted device
a3532f0a-635e-4048-b225-1fe04153ebb3, confirmed deleted — devices_get on that id returns
Not Found.
Three surfaces, same flow, same wrong answer
| Surface |
Response |
Verdict |
flows_get_broken |
{"count": 0, "broken": []} |
False negative |
advanced_flows_list → broken |
false |
False negative |
advanced_flows_validate |
{"valid": true, "errors": [], "warnings": []} |
False negative |
| Manual reference resolution |
3 cards → deleted device |
Correct |
Individual defects
B1 — flows_get_broken does not resolve card references [CRITICAL]
The core defect. Returns an empty list on an installation with four genuinely broken flows. Because
it inherits flow.broken, it only ever reports what Homey itself already decided — and Homey did
not set that flag for any deleted-device case observed here.
Independent confirmation that the breakage was real: the Flow Checker app, running against the same
Homey, did flag one of these flows under “Kapotte ingeschakelde flows” while the MCP reported zero.
B2 — broken field in list tools is always false [MISLEADING]
flows_list and advanced_flows_list both expose a broken boolean. Across all 99 flows it was
false — including the intentionally broken one. A field that is structurally incapable of being
true is worse than no field, because it implies a check was performed. Either populate it from a
real check or drop it.
B3 — advanced_flows_validate validates graph shape only [GAP]
Passing the exact card map of the broken reference flow returns valid: true with no errors and no
warnings. The tool checks ids, edges and reachability, but never asks whether a card’s uri
actually exists. Since its stated purpose is to catch problems before creating a flow, this is
where a bad device reference should surface first.
Suggested: add a resolve_references option (default on) that reports unknown card uris as errors
and app-disabled cards as warnings.
B4 — No reference check for args.flow.id (deleted sub-flows) [CRITICAL]
This class is missed by everything, including the Flow Checker app. A programmatic_trigger
action stores its target as:
{"flow": {"id": "c93cbe0c-3d5a-4aca-9004-bd5facce54ff",
"name": "Module-buiten-motion",
"type": "advanced"}}
That flow was deleted. Three such cards across two enabled flows pointed at it. Nothing warned the
user, and the card still renders the stale cached name, so the editor looks correct. For users who
build modular flows out of sub-flows this is the most damaging failure mode, and the one with the
best payoff: the cached name is right there in the args and makes an excellent error message.
B5 — Installed-but-disabled apps are not treated as breakage [GAP]
An app can be installed while enabled: false / state: "stopped". Its cards then vanish from the
live registry and any flow using them silently never fires — but the app is still “installed”, so an
installed-check passes. Two enabled flows here depended on cards from a disabled app. This should be
reported as recoverable breakage, distinct from a deleted device.
What a correct check must resolve
Five checks cover every failure mode observed. Each is a lookup against data the API already returns.
| # |
Check |
Method |
Catches |
| C1 |
Device still exists |
Parse homey:device:<uuid>:<card>, test uuid against getDevices() |
Deleted devices |
| C2 |
Card definition still exists |
Test full card id against the union of the three card registries |
Changed devices, app updates that drop cards, disabled apps |
| C3 |
Referenced flow still exists |
Test args.flow.id against basic + advanced flows |
Deleted sub-flows (B4) |
| C4 |
Owning app is runnable |
For homey:app:*, check enabled, state, crashed — not merely installed |
Disabled and crashed apps (B5) |
| C5 |
Argument references resolve |
Resolve UUID-shaped args values against devices, zones, users, moods, logic variables, flows |
Dangling zone / variable / user references |
C2 is the highest-value check. The registry covers device cards too — all 3,157 of them — so it
catches a device that still exists but lost a capability after a driver or firmware update. That is
the “device changed” case, as opposed to “device deleted”.
On false positives. A naive UUID sweep over card args produced 120 hits on this installation,
of which 108 were legitimate references to users, moods and logic variables. Resolving against all
six entity types brought it to 12, and every one was real. C5 is only worth shipping with the
full resolution set.
Suggested response shape
Flow-level granularity is not actionable — the real-world case here was an Advanced Flow with 40
cards. Report the card, its type and its canvas coordinates so the user can find it. Separate hard
breakage from recoverable, and enabled flows from disabled ones, so a deliberately parked flow does
not read as an incident.
{
"count": 2,
"broken": [
{
"flow_id": "a31aab5d-d003-43fe-83f0-fd1e7176ebea",
"flow_name": "Knopen control center gang inkom",
"type": "advanced",
"enabled": true,
"severity": "broken",
"cards": [
{
"card_id": "homey:device:3e276abd-...:on",
"card_type": "condition",
"x": 400, "y": 980,
"reason": "DEVICE_MISSING",
"missing_ref": "3e276abd-6386-46fd-b11a-e4de88d39166"
}
]
},
{
"flow_id": "88a742e5-d2f3-4484-b2ed-6d5774312260",
"flow_name": "Buiten verlichting auto",
"type": "advanced",
"enabled": true,
"severity": "broken",
"cards": [
{
"card_id": "homey:manager:flow:programmatic_trigger",
"card_type": "action",
"x": 800, "y": 840,
"reason": "FLOW_MISSING",
"missing_ref": "c93cbe0c-3d5a-4aca-9004-bd5facce54ff",
"cached_name": "Module-buiten-motion"
}
]
}
]
}
Useful reason values: DEVICE_MISSING, CARD_MISSING, FLOW_MISSING, APP_MISSING,
APP_DISABLED, APP_CRASHED, ARG_UNRESOLVED.
Useful severity values: broken (unrecoverable without editing) and degraded (recoverable, e.g.
re-enable the app).
Reference implementation
The check that found all four problems. Run as HomeyScript against the live Homey. Roughly 40 lines
of actual logic; the bulk is assembling the resolution sets.
const devices = await Homey.devices.getDevices();
const apps = await Homey.apps.getApps();
const flows = await Homey.flow.getFlows();
const advs = await Homey.flow.getAdvancedFlows();
const zones = await Homey.zones.getZones();
const users = await Homey.users.getUsers();
const moods = await Homey.moods.getMoods();
const vars = await Homey.logic.getVariables();
// C2: the live card registry — includes device cards
const t = await Homey.flow.getFlowCardTriggers();
const c = await Homey.flow.getFlowCardConditions();
const a = await Homey.flow.getFlowCardActions();
const registry = new Set([
...Object.keys(t), ...Object.keys(c), ...Object.keys(a)
]);
const devIds = new Set(Object.keys(devices));
const known = new Set([ // C5 resolution set
...devIds, ...Object.keys(apps), ...Object.keys(zones),
...Object.keys(flows), ...Object.keys(advs), ...Object.keys(users),
...Object.keys(moods), ...Object.keys(vars)
]);
for (const f of Object.values(advs)) // advanced-flow node ids
Object.keys(f.cards || {}).forEach(id => known.add(id));
function checkCard(id, args, report) {
if (typeof id !== 'string') return;
if (/^homey:(manager:logic:(all|any)|advancedflow:)/.test(id)) return;
const parts = id.split(':');
// C1 — device deleted
if (parts[1] === 'device' && !devIds.has(parts[2]))
return report('DEVICE_MISSING', parts[2]);
// C2 — card definition gone (covers C4: disabled apps drop out of registry)
if (!registry.has(id))
return report('CARD_MISSING', id);
// C3 / C5 — argument references
for (const [key, val] of Object.entries(args || {})) {
const ref = (val && typeof val === 'object') ? val.id
: (typeof val === 'string' ? val : null);
if (!ref || !/^[0-9a-f-]{36}$/i.test(ref)) continue;
if (!known.has(ref))
report(key === 'flow' ? 'FLOW_MISSING' : 'ARG_UNRESOLVED', ref, val.name);
}
}
Iterate basic flows over trigger / conditions / actions, advanced flows over
Object.values(flow.cards), skipping type: "note" nodes.
Secondary observations
No flows_validate for basic flows
advanced_flows_validate has no counterpart for basic flows, so there is no dry-run path at all
before flows_create. Worth adding alongside the B3 fix, sharing the same resolver.
Verified working
For balance: everything used to build this report behaved correctly. devices_list, devices_get,
apps_get, apps_get_settings, flows_get, advanced_flows_get and homeyscript_run all
returned accurate, complete data. devices_get returning a clean Not Found for the deleted uuid
is what made the diagnosis straightforward. The defect is narrowly in the flow-integrity surfaces,
not the underlying API access.
Compiled from a full scan of 99 flows on a live Homey Pro, 8 August 2026. Findings verified by
re-scan after user fixes and cross-checked against Flow Checker 1.37.2. No flows were modified
during analysis.