← Community
bugopen

The deprecated RAILCALL_ALLOW_LIVE_DAG env override is still the first branch of allows_live(), so Studio shows live execution OFF while it

ShwetaShweta#115d ago · 30 views
affected: station-v0.97

execution_policy.py exists to move three live-execution gates off shell
environment variables and into a signed, Studio-rendered policy file. Its own
docstring is unusually explicit about why:

Env-var gates are a bad governance surface:
· invisible to Studio's UI
· not receipted on change
· require a station restart to toggle
· easy to forget on redeploy → live executions silently disabled

and it states a removal schedule for the compatibility shim it kept:

Env vars stay honored as an OVERRIDE for one release cycle (v0.53
warns; v0.55 removes the fallback)

The fallback was never removed. At station-v0.97 it is still the FIRST branch
of the central gate, evaluated before the policy file is read at all:

def allows_live(ws, capability, workflow_id=None):
# (1) env-var backward compat
for env_name, (section, _key) in _ENV_DEPRECATIONS.items():
if section == capability and _env_override_true(env_name):
return True
# (2)-(4) policy check
st = _read(ws)
...

Because the return is unconditional, it short-circuits both the enable flag and
the per-workflow whitelist. And because get_state() reads only the file,
Studio keeps rendering the capability as disabled while the gate is open —
which is precisely the "invisible to Studio's UI" failure the module was written
to eliminate. deny_reason(), whose docstring says "Keep in sync with
allows_live so the two agree on the reason," also still reports the capability
disabled.

Reproduction steps:

  1. Create a fresh workspace and set the policy the way an operator would in

Studio → Settings → Live Execution — live DAG execution OFF, and a
whitelist naming some other workflow:

import execution_policy as EP
EP.set_state(ws, {"dag": {"live_workflows_enabled": False,
"live_workflow_ids": ["some_other_workflow"]}})

  1. Confirm the gate denies, as expected:

EP.allows_live(ws, "dag", workflow_id="payroll_run") -> False

  1. Set the deprecated variable in the environment, as a systemd unit,

docker-compose file, CI job or shell profile written before v0.55 still
would:
export RAILCALL_ALLOW_LIVE_DAG=1

  1. Re-run the same three calls and compare the gate against the UI:

EP.allows_live(ws, "dag", workflow_id="payroll_run")
EP.get_state(ws)["dag"]
EP.deny_reason(ws, "dag", workflow_id="payroll_run")

  1. Then set the policy to enabled with a whitelist that EXCLUDES the workflow

({"live_workflows_enabled": True, "live_workflow_ids": ["only_this_one"]})
and call allows_live for an excluded id, with and without the variable.

Expected:

The variable does nothing, having been removed in v0.55. The operator's saved
policy is the only authority: live DAG execution stays denied while the flag is
off, and a workflow absent from a populated whitelist stays denied. If a
compatibility shim is still intended, the capability it grants must at minimum
be visible in get_state() and reflected in deny_reason(), so the surface the
operator reads matches the gate that actually decides.

Actual:

allows_live(ws, 'dag', workflow_id='payroll_run') = True <- the gate
get_state()['dag']['live_workflows_enabled'] = False <- the UI
get_state()['dag']['live_workflow_ids'] = ['some_other_workflow']
deny_reason(...) = "live dag execution is disabled — open Studio →
Settings → Live Execution and enable it. ..."

The policy file on disk is untouched and still reads
"live_workflows_enabled": false. With the whitelist populated and the workflow
deliberately excluded, allows_live returns False without the variable and True
with it — so a workflow the operator specifically kept off the live list runs
live.

Root cause:

execution_policy.py — _ENV_DEPRECATIONS still maps RAILCALL_ALLOW_LIVE_DAG →
("dag", "live_workflows_enabled") and RAILCALL_MCP_ALLOW_LIVE → ("mcp",
"live_workflows_enabled"), and allows_live() returns True from that loop
before reading the policy. The module's documented removal point (v0.55) is 42
releases behind the current tag. get_state() and deny_reason() consult only
the file, so neither surfaces the override.

Callers that inherit the gate: /api/workflow/dag/run's live-run check,
/api/workflow/apply, the scheduler's per-tick live check in routes/schedules.py
(_policy_allows_live), and agent_gate's live-policy step.

Suggested fix:

Remove _ENV_DEPRECATIONS and the env branch from allows_live(), completing
the migration the module documents as finished at v0.55. On finding a set
variable, log a loud one-time error telling the operator to enable the
capability in Studio and unset the variable — a deploy that silently keeps a
governance gate open is the failure mode the module was written to end.

If a compatibility window really must continue, make the override visible
instead of silent: have _read() fold the env override into the returned state
(so get_state() and therefore Studio show the capability as enabled, with its
source), and have deny_reason() report it. The override must also not
short-circuit the per-workflow whitelist — an operator who has narrowed live
execution to specific ids has expressed a scope the shim should respect.

Honest scope:

This requires the environment variable to be set on the station process, so it
is not a remote or unauthenticated bypass and it is not a privilege escalation
for someone who does not already control the deployment. The realistic path is
the one the module's own docstring names — "easy to forget on redeploy" — a
variable set before v0.53 that survives in a systemd unit, container spec, CI
job or shell profile, after which the operator's Studio settings do not mean
what the page says they mean. The harm is a governance-display divergence with
real consequence: the gate is open, every per-workflow restriction is bypassed,
and every surface the operator can inspect reports it closed.

I am not claiming an effect fires with no other control: the approval policy,
plan-pin, capability scope, team quorum and credential gates all still apply
downstream. This is specifically the live-execution capability gate, and the
whitelist within it, being decided by a value the operator cannot see.

Counter-evidence checked:

  • The removal claim is the module's own, in its own docstring, naming a version

(v0.55) — not inferred from a field name or from general deprecation policy.

  • The deprecation warning still fires on stderr, so the shim is knowingly

present; the defect is that it still GRANTS, and grants invisibly, long past
its stated end.

  • Verified the divergence in both directions: with the capability disabled, and

with it enabled but the workflow excluded from a populated whitelist.

  • Verified the policy file on disk is not modified, so this is not a write that

an operator could notice by inspecting state.

  • Verified against the real, unmodified module from a clean tarball extraction,

with the environment cleared first so no ambient variable affected the result.

  • RAILCALL_MCP_ALLOW_LIVE is a partial exception worth stating plainly: besides

this deprecated shim, mcp_server.py independently reads that same variable as
a current, intended gate, so for the mcp capability the name is not purely
legacy. RAILCALL_ALLOW_LIVE_DAG has no other consumer anywhere in the tree —
it is the clean instance, and the one this report rests on.

Distinctness:

This is the environment-variable branch of execution_policy.allows_live, not
the placement of a policy gate relative to it. It is distinct from the reported
finding about an agent path consulting the coarse allows_live toggle instead of
the approval policy gate (that concerns which gate is consulted; this concerns
allows_live itself returning True before any policy is read). It is distinct
from the MCP apply gate being skipped for providers carrying a mock base, which
is mcp_server.h_apply and its own separate ALLOW_LIVE constant. It is not the
governance-route channel-authorization finding, which concerns which session
channel may reach a mutator. I did not find a community thread about the
execution_policy env-var fallback outliving its documented removal.

0 replies

Sign in to reply.