Commit 34f2de8
## PR Type
- [x] Bug fix
## Summary
When `@schedule` is present but all scheduling flags are falsy (e.g.
`@schedule(daily=False)`), deploying to Argo Workflows crashes with
`AttributeError: 'NoneType' object has no attribute 'split'` and no
WorkflowTemplate is created. This fix makes the deploy succeed and skip
CronWorkflow creation, which is the expected behaviour when no schedule
is configured.
## Issue
Fixes #3121
## Reproduction
**Runtime:** argo
**Commands to run:**
```bash
python myflow.py argo-workflows create
```
Where `myflow.py` contains:
```python
from metaflow import FlowSpec, step, schedule
@schedule(daily=False)
class MyFlow(FlowSpec):
@step
def start(self):
self.next(self.end)
@step
def end(self):
pass
if __name__ == "__main__":
MyFlow()
```
**Where evidence shows up:** parent console / `argo-workflows create`
output
<details>
<summary>Before (error / log snippet)</summary>
```
AttributeError: 'NoneType' object has no attribute 'split'
File ".../metaflow/plugins/argo/argo_workflows.py", line 463, in _get_schedule
return " ".join(schedule.schedule.split()[:5]), schedule.timezone
```
</details>
<details>
<summary>After (evidence that fix works)</summary>
```
Workflow template 'MyFlow' created successfully.
No CronWorkflow created (no schedule configured).
```
</details>
## Root Cause
`ScheduleDecorator.flow_init` sets `self.schedule = None` when all
scheduling flags are falsy (`schedule_decorator.py:49`).
`_get_schedule()` in `argo_workflows.py` guarded against the decorator
being absent (`if schedule:`), but not against `schedule.schedule` being
`None`. Calling `.split()` on `None` raises `AttributeError`.
Separately, `trigger_explanation()` checked decorator presence rather
than `self._schedule`, so it would incorrectly report CronWorkflow-based
triggering even when no schedule was resolved.
## Why This Fix Is Correct
`_get_schedule()` now returns `(None, None)` when `schedule.schedule is
None`, treating it identically to the decorator being absent. The rest
of the deploy path already handles `self._schedule is None` correctly —
no CronWorkflow is created. `trigger_explanation()` now checks
`self._schedule is not None`, which is the actual resolved value, rather
than decorator presence.
## Failure Modes Considered
1. **Flows with a real schedule are unaffected** — the
`schedule.schedule is None` guard is only entered when all flags are
falsy; any valid cron string, `daily=True`, `hourly=True`, or
`weekly=True` sets a non-None value and takes the existing return path.
2. **Backward compatibility** — the previous behaviour for the
`@schedule(daily=False)` case was a crash, so changing it to a clean
no-op is strictly an improvement with no risk of regression.
## Tests
- [x] Unit tests added/updated
- [x] Reproduction script provided (required for Core Runtime)
- [ ] CI passes
- [ ] If tests are impractical: explain why below and provide manual
evidence above
The bug is straightforward to reproduce with the script above. Unit test
coverage for `_get_schedule()` with a `None` schedule value would be a
good addition.
## Non-Goals
This fix does not address the Step Functions path (which returns
`schedule.schedule` directly and handles `None` safely already), does
not implement an `--no-enable-schedule` flag (see PR #3102), and does
not change any scheduling behaviour for flows where a schedule is
actually configured.
## AI Tool Usage
- [x] AI tools were used (describe below)
OpenCode (Claude Sonnet) was used to identify the bug, draft the issue,
and generate the fix. All generated code was reviewed and understood
before committing.
---------
Co-authored-by: Shashank Srikanth <ssrikanth@netflix.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fc8f853 commit 34f2de8
2 files changed
Lines changed: 111 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
460 | 460 | | |
461 | 461 | | |
462 | 462 | | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
463 | 468 | | |
464 | 469 | | |
465 | 470 | | |
| |||
482 | 487 | | |
483 | 488 | | |
484 | 489 | | |
485 | | - | |
| 490 | + | |
486 | 491 | | |
487 | 492 | | |
488 | 493 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
1 | 3 | | |
2 | 4 | | |
3 | 5 | | |
| 6 | + | |
4 | 7 | | |
5 | 8 | | |
6 | 9 | | |
| |||
27 | 30 | | |
28 | 31 | | |
29 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
0 commit comments