Summary
workflows delete (NIP-09 a-tag deletion targeting a kind:30620 workflow definition) genuinely deletes the workflow's row from the operational workflows table the scheduler reads, but workflows list queries the raw kind:30620 Nostr events directly with no deletion-state filtering — so a successfully deleted workflow keeps appearing in list forever ("zombie" listing). No scheduling risk (the scheduler can't see a row that's gone), but it's a confusing/incorrect list result after a confirmed-accepted delete.
Root cause
buzz workflows delete publishes a NIP-09 kind:5 deletion event with an a-tag coordinate 30620:<pubkey>:<workflow_id> (crates/buzz-cli/src/commands/workflows.rs:139-144 → buzz_sdk::build_workflow_delete → build_delete_addressable, crates/buzz-sdk/src/builders.rs:1503).
- The relay ingests it in
handle_a_tag_deletion (crates/buzz-relay/src/handlers/side_effects.rs:2104-2117), which special-cases KIND_WORKFLOW_DEF (30620) to call delete_workflow_for_owner — a hard DELETE FROM workflows WHERE ... (crates/buzz-db/src/workflow.rs:768-788) against the operational workflows table.
buzz workflows list (crates/buzz-cli/src/commands/workflows.rs:13-19, cmd_list_workflows) does not query that table. It sends a raw REQ for kinds:[30620], #h:[channel_id] directly against the event store, with no deletion-state filtering.
- The relay's own comment at
side_effects.rs:2151-2158 explains the split is deliberate: workflow a-tag deletion is called out as not soft-deleting the underlying events row "by design", unlike every other addressable/parameterized-replaceable kind, which does soft-delete the live (kind, pubkey, d_tag) row specifically "so REQs stop returning it."
Impact
- No scheduling risk:
list_enabled_channel_workflows / list_all_enabled_workflows (crates/buzz-db/src/workflow.rs:425-457, the latter doc-commented as used by the cron scheduler) filter against the same workflows table the delete removes the row from, so a deleted workflow cannot fire.
- User-facing confusion:
workflows delete reports accepted: true (correctly — the deletion event is valid and its effect on the scheduler is real), but workflows list shows the definition indefinitely afterward, with no way to distinguish "actually still active" from "deleted but zombie-listed" via the CLI.
Suggested fix
Bring workflow a-tag deletion in line with the general NIP-33 pattern: also soft-delete the kind:30620 events row (matching (kind, pubkey, d_tag)) when delete_workflow_for_owner succeeds, the same way every other addressable kind's deletion path already does. Alternatively, have cmd_list_workflows cross-reference the workflows table (or an equivalent still-active check) before including an entry.
Repro
- Create a workflow, confirm it in
buzz workflows list.
buzz workflows delete <id> — returns accepted: true.
buzz workflows list — the deleted workflow is still present.
Happy to provide more detail if useful; found this while investigating a "zombie workflow" report in our own deployment (larreala/AgenticOS#67).
Summary
workflows delete(NIP-09 a-tag deletion targeting a kind:30620 workflow definition) genuinely deletes the workflow's row from the operationalworkflowstable the scheduler reads, butworkflows listqueries the raw kind:30620 Nostr events directly with no deletion-state filtering — so a successfully deleted workflow keeps appearing inlistforever ("zombie" listing). No scheduling risk (the scheduler can't see a row that's gone), but it's a confusing/incorrectlistresult after a confirmed-accepted delete.Root cause
buzz workflows deletepublishes a NIP-09 kind:5 deletion event with ana-tag coordinate30620:<pubkey>:<workflow_id>(crates/buzz-cli/src/commands/workflows.rs:139-144→buzz_sdk::build_workflow_delete→build_delete_addressable,crates/buzz-sdk/src/builders.rs:1503).handle_a_tag_deletion(crates/buzz-relay/src/handlers/side_effects.rs:2104-2117), which special-casesKIND_WORKFLOW_DEF(30620) to calldelete_workflow_for_owner— a hardDELETE FROM workflows WHERE ...(crates/buzz-db/src/workflow.rs:768-788) against the operationalworkflowstable.buzz workflows list(crates/buzz-cli/src/commands/workflows.rs:13-19,cmd_list_workflows) does not query that table. It sends a rawREQforkinds:[30620], #h:[channel_id]directly against the event store, with no deletion-state filtering.side_effects.rs:2151-2158explains the split is deliberate: workflow a-tag deletion is called out as not soft-deleting the underlyingeventsrow "by design", unlike every other addressable/parameterized-replaceable kind, which does soft-delete the live(kind, pubkey, d_tag)row specifically "so REQs stop returning it."Impact
list_enabled_channel_workflows/list_all_enabled_workflows(crates/buzz-db/src/workflow.rs:425-457, the latter doc-commented as used by the cron scheduler) filter against the sameworkflowstable the delete removes the row from, so a deleted workflow cannot fire.workflows deletereportsaccepted: true(correctly — the deletion event is valid and its effect on the scheduler is real), butworkflows listshows the definition indefinitely afterward, with no way to distinguish "actually still active" from "deleted but zombie-listed" via the CLI.Suggested fix
Bring workflow a-tag deletion in line with the general NIP-33 pattern: also soft-delete the kind:30620
eventsrow (matching(kind, pubkey, d_tag)) whendelete_workflow_for_ownersucceeds, the same way every other addressable kind's deletion path already does. Alternatively, havecmd_list_workflowscross-reference theworkflowstable (or an equivalent still-active check) before including an entry.Repro
buzz workflows list.buzz workflows delete <id>— returnsaccepted: true.buzz workflows list— the deleted workflow is still present.Happy to provide more detail if useful; found this while investigating a "zombie workflow" report in our own deployment (
larreala/AgenticOS#67).