refactor(engine): remove dead workflow lifecycle hooks (#476) - #480
Conversation
The hooks: block (on_start/on_complete/on_error) was parsed and its templates rendered, but the result was always discarded, making the feature entirely unobservable. Remove HooksConfig, WorkflowDef.hooks, the engine's _execute_hook/LifecycleHookResult and all call sites, and the Hooks section of the workflow syntax docs. A workflow declaring hooks: now fails validation with a clear error.
…le-hooks # Conflicts: # CHANGELOG.md
|
@microsoft-github-policy-service agree |
Jason Robert (jrob5756)
left a comment
There was a problem hiding this comment.
Two blocking issues here, both real: a mangled comment that undoes the readability this refactor was supposed to deliver, and a bundled skill that still teaches agents to write a hooks: block that now hard-fails validation. Once those are fixed this is fine to merge — the three recommended items are cleanup, not blockers.
Blocking:
src/conductor/engine/workflow.py:5343— duplicated lines and a broken sentence in theexcept BaseExceptioncomment (see inline).- No single line — the
conductorskill (SKILL.md,authoring.md,yaml-schema.md) still documentshooks:, and a workflow using it now fails to load instead of just silently no-opping like before this PR.
b2 — BLOCKING: the bundled conductor skill still teaches agents to write hooks:, which now hard-fails validation
docs/workflow-syntax.md got cleaned up, but the skill that ships with the wheel didn't. plugins/conductor/skills/conductor/skills/conductor/SKILL.md:111, references/authoring.md:51-54, and references/yaml-schema.md:133-138 all still document the removed hooks: block — and this is the file skills/registry.py loads whenever a workflow declares skills: [conductor].
This is worse than typical doc drift because of how the removal was implemented. WorkflowDef uses extra="forbid", so hooks: doesn't degrade to a no-op anymore — it aborts the load. I confirmed it: a workflow with workflow.hooks: fails conductor validate with exit code 1 and workflow.hooks: Extra inputs are not permitted. So an agent following this skill will generate a workflow that can't run at all. Before this PR, the same generated YAML at least executed.
I checked that these are genuinely the removed workflow-level feature and not the unrelated plugin hooks/ directories (plugins/manifest.py, config/validator.py, README.md:229, examples/plugins.yaml:39 are correctly untouched) — these three references are specifically workflow:-scoped hooks: keys with on_start/on_complete/on_error children.
Worth flagging as a process gap too: docs/workflow-syntax.md and these skill references are near-duplicate YAML references, and only the first got updated here. That's exactly how this slipped through, and it'll happen again on the next syntax change unless there's a check for it. A grep -rn 'hooks:' plugins/ docs/ examples/ before merge would have caught it.
Suggested fix: Delete the table row at SKILL.md:111; delete lines 51-54 of authoring.md (and the blank line that's now orphaned); delete lines 133-138 of yaml-schema.md including the # Lifecycle hooks comment, leaving one blank line between the cost: block and the # Arbitrary metadata for downstream tooling comment.
r1 — RECOMMENDED: stale "on_error hooks" prose survives in three places the sweep missed
The parallel wording got scrubbed correctly at engine/workflow.py:2109, engine/workflow.py:2136, and docs/workflow-syntax.md:1158, but three mirrors of the same sentences didn't:
src/conductor/exceptions.py:664— theSubworkflowTerminatedErrordocstring still says the attributes are carried "...so on_error hooks, debugging surfaces, and the CLI can inspect it without walking__cause__." This one's worth more attention than a normal stale comment, since it's the stated reasonterminated_output/terminated_reason/terminated_bylive on the exception at all. Point at a consumer that no longer exists and someone will eventually read this as dead code and strip real diagnostic payload off a sub-workflow failure.AGENTS.md:216— the Terminate steps bullet still says "; runson_completehook" and ", runson_errorhook." This file gets loaded into agent context on basically every task in this repo, so the stale claim has wide reach.plugins/conductor/skills/conductor/references/authoring.md:657, 658, 660— "Runs theon_completehook.", "Runs theon_errorhook.", and "...foron_errorhooks and debugging surfaces." Line 660 is the exact sentence already fixed atdocs/workflow-syntax.md:1158— just not here.
All three describe behavior that doesn't happen anymore.
Suggested fix: Match the wording already used in docs/workflow-syntax.md:1158. In exceptions.py:664: "the wrapper so debugging surfaces and the CLI can inspect it without walking __cause__." In AGENTS.md:216: drop "; runs on_complete hook" and ", runs on_error hook". In authoring.md: drop the "Runs the ... hook." sentences at 657 and 658, and on 660 change "for on_error hooks and debugging surfaces" to "for debugging surfaces".
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #480 +/- ##
=======================================
Coverage ? 91.77%
=======================================
Files ? 146
Lines ? 23844
Branches ? 0
=======================================
Hits ? 21882
Misses ? 1962
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Fix garbled except BaseException comment left by the removal, restoring the microsoft#116 rationale and documenting the is_base_exception flag. - Remove hooks: from the bundled conductor skill (SKILL.md, authoring.md, yaml-schema.md) so agents no longer generate workflows that hard-fail. - Scrub remaining stale 'on_error/on_complete hooks' prose in exceptions.py, AGENTS.md, authoring.md, and a test docstring. - Add a WorkflowDef model validator giving a targeted microsoft#476 error naming the removal and the workflow_completed/workflow_failed replacement, instead of the generic extra_forbidden message. - Rework/extend tests: schema, loader (YAML), and CLI (conductor validate) now assert the removal message; add invalid_hooks.yaml fixture. - Fix CHANGELOG spacing.
|
Thanks for the thorough review — all five items addressed in 73e3b9d. Blocking: B1 (garbled except BaseException comment) — restored the comment using your suggested text, including the is_base_exception paragraph documenting what the flag is for. The duplicated lines and cut-off clause are gone; no executable lines were touched. Recommended: R1 (stale "on_error hooks" prose) — reworded exceptions.py (the SubworkflowTerminatedError docstring), AGENTS.md:216, and authoring.md:657-660 to match the wording already used in docs/workflow-syntax.md. I also found and fixed one more the sweep missed: a docstring in tests/test_engine/test_subworkflow.py. On the process gap: agreed a grep -rn 'hooks:' plugins/ docs/ examples/ would've caught B2. I ran that sweep this round and it's clean apart from the intended references (the validator message and the CHANGELOG entry). |
|
CI note: the one failing check (Test (Python 3.13, ubuntu-latest)) is a flaky Fleet TUI test, not a regression from this PR. It failed on tests/test_fleet/test_tui_runs.py::TestRunsScreenPausesWhileNotOnTop::test_the_animation_timer_is_paused_and_resumed — a timing assertion on the splash animation timer. An earlier run of this same branch failed instead on a different set of Fleet TUI tests (test_tui_theme.py::TestShortenHome and test_tui_actions.py::TestDirectoryPickerModal, on the Windows job). Different platform, different tests, all under tests/test_fleet/ — the inconsistency across runs points to pre-existing flakiness/platform-fragility in the Fleet TUI suite rather than anything this change introduced. For reference, this PR's changes (git diff --name-only upstream/main...HEAD) don't touch fleet/ or tui/ at all — they're limited to config/schema, engine, exceptions, the bundled conductor skill docs, and the corresponding tests. Every test this PR adds or modifies passes on all jobs. A re-run of the failed job should likely go green; happy to open a separate issue for the flaky Fleet tests if that's useful. |
Jason Robert (jrob5756)
left a comment
There was a problem hiding this comment.
LGTM, thanks for contributing!
6cdbabb
into
microsoft:main
Thank you! I’m really interested in contributing more to the project. How can I connect with you to collaborate on issues and work on such repositories? I’d love to learn from the community and contribute more. Looking forward to hearing from you! |
Closes #476.
What
The
hooks:block (on_start/on_complete/on_error) was documented and parsed, but the feature was dead code: hook templates were rendered and the result discarded. Nothing observed it — no event, no log entry, no dashboard/console output, no context write, no side effect — so a hook was completely unobservable, including when it failed.Changes
HooksConfigandWorkflowDef.hooksfromconfig/schema.py, and the re-exports fromconfig/__init__.py._execute_hook,LifecycleHookResult, and all call sites fromengine/workflow.py, plus now-stale docstring references.docs/workflow-syntax.md.tests/fixtures/valid_full.yaml); addedTestHooksRemovedasserting a workflow declaringhooks:now fails validation with a clear error.Acceptance criteria
hooks:removed from the schema — declaring it produces a clear validation error (extra_forbidden)._execute_hook/LifecycleHookResultand all call sites removed from the engine.docs/workflow-syntax.md.Notes
If lifecycle hooks are wanted later, the right syntax should be designed against a concrete requirement (emitting an event, invoking a
type: scriptstep, or calling a webhook) rather than restoring a placeholder that renders a template and throws it away.Because
hooks:was documented and previously parsed (silently doing nothing), this is technically a breaking change for any workflow that declares it — those will now fail validation, which is the intended behavior per the issue.Verified locally:
ruff check/formatclean, affected test suites green. The remainingtydiagnostics are pre-existing Windows-only platform issues (termios/signal.SIGKILL/etc.) present on a clean checkout, unrelated to this change.