Skip to content

fix(workflows): match n8n workflows by exact name and stop duplicate imports - #5554

Open
bettercalln1ck wants to merge 1 commit into
Osmantic:mainfrom
bettercalln1ck:fix/4191-workflow-name-match
Open

bettercalln1ck wants to merge 1 commit into
Osmantic:mainfrom
bettercalln1ck:fix/4191-workflow-name-match

Conversation

@bettercalln1ck

Copy link
Copy Markdown
Contributor

Summary

Fixes #4191.
Fixes #4188.

Closes #4191 and #4188 — they are two halves of one defect, and fixing either alone leaves the bug standing.

Matching (#4191). Three sites resolved a catalog entry to an n8n workflow with a substring test and took the first hit:

# api_workflows (:129)
if wf_name_lower in n8n_name or n8n_name in wf_name_lower:
# _remove_workflow (:238) and workflow_executions (:288)
if wf_name_lower in wf.get("name", "").lower():

I checked whether the loose match was load-bearing: all 18 shipped catalog entries have a name identical to the name inside their template JSON, so an exact match resolves every one of them. The substring test bought nothing.

What it cost is picking a workflow the operator did not mean. There are no substring collisions within the catalog — the realistic collision is a workflow the user owns. n8n's own duplicate action names a copy "<name> copy", which contains the catalog name, and _remove_workflow is the disable path: first-match-wins there deletes the user's copy instead of the ODS-managed workflow.

Replaced with one find_n8n_workflow() helper doing an exact, case- and whitespace-insensitive name match, used at all three sites.

Duplicates (#4188). enable_workflow POSTed a new workflow to n8n unconditionally, so re-enabling an already-installed workflow left another copy behind, sharing a name — manufacturing precisely the ambiguity the matcher above then has to resolve. It now looks for an existing workflow by exact name first, activates that one if found, and returns alreadyInstalled: true without creating anything.

The two are causally linked: correct matching over duplicated names is still a coin flip, and de-duplicating without exact matching still deletes the wrong thing.

AI Assistance

AI-assisted: an AI coding assistant drafted the patch and the regression test and ran the validation recorded below. I reviewed the diff, chose the validation, and am accountable for the change.

Release Lane

  • Stable hotfix targeting release/2.6.x
  • Mainline change targeting main
  • Next-minor work targeting the next feature/minor release
  • Not sure; reviewer should help classify

Stable hotfix reason:

n/a

Changed Surface

  • Docs only
  • Tests only
  • Dashboard UI
  • Dashboard API / host agent
  • Installer / bootstrap / lifecycle
  • Docker Compose / service manifests
  • Model routing / Hermes / capabilities
  • Network exposure / auth / proxy
  • Dependencies / runtime wiring

Risk And Validation

  • Risk level: Medium
  • Validation run:
    • git diff --check
    • Markdown/link sanity for docs
    • Focused tests listed below
    • Dashboard lint/test/build
    • Extension audit / compose validation
    • Release-grade fleet or scoped hardware validation
    • Stable-lane patch validation, if targeting release/2.6.x
    • Not required because: confined to one router; the whole dashboard-api suite is green and the catalog/template name equality that makes exact matching safe was verified across all 18 shipped workflows.

Commands/results:

$ pytest tests/test_workflows.py -q
47 passed

$ pytest tests/ -q                       # the 'api' CI job
2140 passed, 1 skipped

# both new tests fail with the router restored from origin/main
$ git checkout origin/main -- ods/extensions/services/dashboard-api/routers/workflows.py
$ pytest tests/test_workflows.py -q
FAILED tests/test_workflows.py::test_find_n8n_workflow_matches_exact_name_only
FAILED tests/test_workflows.py::test_enable_existing_workflow_does_not_create_a_duplicate
2 failed, 45 passed

# catalog name == template name, for every shipped workflow
$ (compared catalog.json entries against each referenced template's "name")
mismatches: 0 / 18

$ make lint
All lint checks passed.
$ ruff check ods/ --select E,F,W --ignore E501,E701,E731,E741,E402
All checks passed!

# run on Python 3.11, matching the workflow

Operational Change Check

  • This is not an operational change.
  • This is an operational change and validation is recorded above.
  • This is an operational change and validation is intentionally deferred for:

Notes For Reviewers

  • test_find_n8n_workflow_matches_exact_name_only orders the list so that a first-match-wins substring search would return the user's "Daily Digest copy", then asserts the ODS workflow is returned instead. It also pins that case and surrounding whitespace still match, and that a catalog name merely containing an installed name ("Digest" vs "Daily Digest") is not a match.
  • Three existing enable tests were modified, and it is worth knowing why: enable_workflow now legitimately queries n8n before creating, which their aiohttp mocks did not model (they stub post/patch, not get). They now stub get_n8n_workflows to report nothing installed, which preserves their original intent — they exercise the create path. No assertion was weakened.
  • If you would rather these were two PRs, I can split them, but the second would conflict with the first in this file and a reviewer would be looking at half a fix in each.
  • These tests gate: dashboard.yml:60 runs the dashboard-api suite.

…imports

Two halves of one defect.

Matching (Osmantic#4191): three sites resolved a catalog entry to an n8n workflow with
a substring test and took the first hit — api_workflows, _remove_workflow and
workflow_executions. All 18 shipped catalog entries have a name identical to
the name inside their template JSON, so the loose match bought nothing. What it
cost is a user's own workflow getting picked instead: n8n's duplicate action
names a copy "<name> copy", which contains the catalog name, so disabling an
ODS workflow could delete the user's copy.

Duplicates (Osmantic#4188): enable_workflow POSTed a new workflow every time, so
re-enabling left several in n8n sharing a name — manufacturing exactly the
ambiguity the matcher then had to resolve. It now activates what is already
installed and reports alreadyInstalled.

Fixing either alone leaves the bug: correct matching over duplicated names is
still a coin flip, and de-duplicating without exact matching still deletes the
wrong thing.

The three existing enable tests now stub get_n8n_workflows to report nothing
installed, since enable legitimately consults n8n before creating.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: Substring workflow name match hits wrong n8n workflow fix: Re-enable always POSTs duplicate n8n workflows

1 participant