Summary
When dotflow start runs in an environment that sets the WORKFLOW_ID variable, DotFlow() ignores it and generates a fresh random UUID anyway. This breaks any integration that expects the runner to adopt a pre-existing workflow id supplied externally (e.g. a managed server that already created the workflow record and scoped an access token to that specific id).
Impact
- Severity: Blocker for every managed integration.
- Scope: Every
dotflow start invocation where the caller relies on WORKFLOW_ID being honoured.
- Symptom: HTTP callbacks from
ServerDefault target a brand-new UUID that does not match the token's scope, so the server rejects every call with 401/403. The workflow runs locally but all task status updates are lost.
Reproduction
Case A — env var ignored
export WORKFLOW_ID=f6f6ca26-b223-4f60-9552-ded50435ac5c
export SERVER_BASE_URL=https://example.com/api/v1
export SERVER_USER_TOKEN=<token scoped to f6f6ca26-...>
dotflow start --step docs_src.basic.simple_cli:simple_step
Runner logs:
POST /api/v1/workflows [401] # new random UUID registered
POST /api/v1/workflows/f6f6ca26-.../tasks [403] # path uses env, body uses new UUID
PATCH /api/v1/workflows/f6f6ca26-.../tasks/01K... [403]
PATCH /api/v1/workflows/f6f6ca26-... [403]
Case B — expected behaviour (currently only works via explicit arg)
from dotflow import DotFlow
workflow = DotFlow(workflow_id="f6f6ca26-b223-4f60-9552-ded50435ac5c")
workflow.task.add(step=my_step)
workflow.start()
This path already skips create_workflow because _externally_provided_id is True. The env-var path should behave the same way.
Root Cause
Two code paths combine to produce the bug.
1. dotflow/core/dotflow.py:51
class DotFlow:
def __init__(self, config=None, workflow_id=None):
self._externally_provided_id = workflow_id is not None
self.workflow_id = workflow_id or uuid4() # <- env var is ignored
2. dotflow/cli/commands/start.py:63
def _new_workflow(self):
storage = self._build_storage()
if storage is None:
return DotFlow() # <- no workflow_id arg
return DotFlow(config=Config(storage=storage)) # <- same
The CLI never forwards an id and the constructor never reads the env, so the runner invents a fresh UUID on every launch. ServerDefault (dotflow/providers/server_default.py) then:
- Calls
POST /workflows with the new UUID — no external system expects this, because the caller believes the workflow already exists with the id stored in WORKFLOW_ID.
- Reports task updates under that new UUID, which every scoped-token integration rejects.
Expected Behaviour
When WORKFLOW_ID is set in the environment, the runner must adopt it as its workflow id and skip the POST /workflows registration call — the same path already taken when workflow_id is passed explicitly to DotFlow(...).
Standalone runs (no env var, no explicit arg) must keep the current behaviour: generate a fresh UUID and register the workflow.
Proposed Fix
In dotflow/core/dotflow.py, fall back to the env var before uuid4():
import os
def __init__(self, config=None, workflow_id=None):
workflow_id = workflow_id or os.getenv("WORKFLOW_ID")
self._externally_provided_id = workflow_id is not None
self.workflow_id = workflow_id or uuid4()
...
This:
- Honours
WORKFLOW_ID when no explicit id is passed.
- Flips
_externally_provided_id to True, so create_workflow is skipped (fixes the 401).
- Uses the same id the external system expects, so all subsequent calls line up (fixes the 403s).
- Does not affect standalone runs — if the env is unset,
os.getenv returns None and the code falls through to uuid4() exactly as today.
Out of Scope
- Adding an explicit
--workflow-id CLI flag — tracked separately if needed.
- Any changes to remote server behaviour. The contract is: if you supply
WORKFLOW_ID, the runner trusts that the id already exists.
Acceptance Criteria
Summary
When
dotflow startruns in an environment that sets theWORKFLOW_IDvariable,DotFlow()ignores it and generates a fresh random UUID anyway. This breaks any integration that expects the runner to adopt a pre-existing workflow id supplied externally (e.g. a managed server that already created the workflow record and scoped an access token to that specific id).Impact
dotflow startinvocation where the caller relies onWORKFLOW_IDbeing honoured.ServerDefaulttarget a brand-new UUID that does not match the token's scope, so the server rejects every call with 401/403. The workflow runs locally but all task status updates are lost.Reproduction
Case A — env var ignored
Runner logs:
Case B — expected behaviour (currently only works via explicit arg)
This path already skips
create_workflowbecause_externally_provided_idisTrue. The env-var path should behave the same way.Root Cause
Two code paths combine to produce the bug.
1.
dotflow/core/dotflow.py:512.
dotflow/cli/commands/start.py:63The CLI never forwards an id and the constructor never reads the env, so the runner invents a fresh UUID on every launch.
ServerDefault(dotflow/providers/server_default.py) then:POST /workflowswith the new UUID — no external system expects this, because the caller believes the workflow already exists with the id stored inWORKFLOW_ID.Expected Behaviour
When
WORKFLOW_IDis set in the environment, the runner must adopt it as its workflow id and skip thePOST /workflowsregistration call — the same path already taken whenworkflow_idis passed explicitly toDotFlow(...).Standalone runs (no env var, no explicit arg) must keep the current behaviour: generate a fresh UUID and register the workflow.
Proposed Fix
In
dotflow/core/dotflow.py, fall back to the env var beforeuuid4():This:
WORKFLOW_IDwhen no explicit id is passed._externally_provided_idtoTrue, socreate_workflowis skipped (fixes the 401).os.getenvreturnsNoneand the code falls through touuid4()exactly as today.Out of Scope
--workflow-idCLI flag — tracked separately if needed.WORKFLOW_ID, the runner trusts that the id already exists.Acceptance Criteria
DotFlow()with no args andWORKFLOW_ID=abcin env yieldsworkflow_id == "abc".DotFlow()with no args and no env var generates a new UUID (current behaviour).DotFlow(workflow_id="xyz")always wins, regardless of env.WORKFLOW_IDis set,server.create_workflowis NOT called.