Change freeze for AI coding agents
During a change window you define, a command that is not read-only and touches production asks a human or is denied. Windows go in your own config.json or in the team policy, work in shadow and enforce mode, and can only tighten.
Install: npx @ursuciprian/reflex setup starts with local rules in shadow mode, no account or key.
Plugins for Claude Code, Codex CLI and opencode: setup guide.
How do I set a deploy freeze or change window for AI coding agents?
Add a freeze list to ~/.config/reflex/config.json, or to the team policy
(.reflex/policy.json) so the whole team gets it: weekly windows such as
{"days": ["fri"], "after": "15:00", "tz": "Europe/Bucharest"} and date ranges such as
{"from": "2026-12-20", "to": "2027-01-03", "outcome": "deny"}. During a window, a command that
is not read-only and touches production (by the working directory, AWS profile, kube context,
Terraform workspace, git branch, the command or a team prod marker) asks a human or is denied, with
a reason such as change freeze: Friday after 15:00 (Europe/Bucharest). It works in shadow and
enforce mode, System 2 never approves it, and it can only tighten: a rule deny stays a deny, and an
invalid window is an error rather than a smaller window. reflex status shows whether a freeze is
active now. This is change management for Claude Code, Codex CLI and the other supported agents,
for their shell commands.
See: GUIDE: change freeze for AI coding agents.
Change freeze for AI coding agents
A deploy freeze for AI coding agents: during a change window you define, a command that is not
read-only and touches production asks a human, or is denied. This is change management for Claude
Code, Codex CLI and the other agents Reflex gates, in the same place as the rest of the policy.
Windows go in ~/.config/reflex/config.json (yours) or in a team policy's freeze list (the
repository's), and both apply:
{
"freeze": [
{"days": ["fri"], "after": "15:00", "tz": "Europe/Bucharest", "applies_to": "prod", "outcome": "ask"},
{"from": "2026-12-20", "to": "2027-01-03", "outcome": "deny", "note": "year-end freeze"}
]
}
| Field | Meaning |
|---|---|
days | Days of the week: mon, tue, wed, thu, fri, sat, sun. |
after, before | Local time, HH:MM. after is inclusive, before is exclusive, and after must be earlier than before; a window across midnight is two windows. |
from, to | Dates, YYYY-MM-DD, both inclusive. |
tz | An IANA time zone name such as Europe/Bucharest. Default UTC. Times and dates are read in it with Intl, so summer time is handled. |
applies_to | prod (default): commands that touch production. all: every command that is not read-only. |
outcome | ask (default) or deny. |
note | Text added to the reason, at most 200 characters. |
Every field a window has must hold at once: {"days": ["fri"], "after": "15:00"} is Friday from
15:00 to midnight. A window needs at least one of days, after, before, from and to.
What counts as production. The markers Reflex already uses: the prod-destroy rule's production
pattern over the working directory, the AWS profile, the kube context, the Terraform workspace, the
git branch and the command text (envs/prod, --context prd-eu, aws_profile=production,
tf_workspace=live and so on), plus the prod markers of the team policy. A command whose
pipelines only write notes (git commit -m "prod fix") is not production.
What the agent sees. A frozen command gets a rule decision with a reason such as
reflex (rule): change freeze: Friday after 15:00 (Europe/Bucharest); production (cwd)
The reason names the kind of marker (cwd, aws_profile, kube_context, command), never its
value, since it can reach a webhook. The trace and reflex audit keep the matched text.
It is a deterministic decision, so it applies in shadow mode too, and in the autonomous profile it is in the always-human class: System 2 never approves it. A human's approval in the approval queue can lift a freeze ask for that one command, but only when the item was parked and answered inside the window, so an approval given before the freeze never carries into it. Nothing lifts a deny.
It only tightens. A window has no outcome that passes. A freeze outcome replaces a pass (the
read-only list excepted), a fast lane pass, a Jev or local judgment, and a rule ask when the window
denies. A rule deny, or a rule ask under an ask window, keeps its own reason. Validation is strict:
an unknown field, a day such as friday, a time such as 9:00, a date that does not exist, an
unknown time zone or an outcome other than ask or deny is an error, never a smaller window.
In a team policy, an invalid window makes the file invalid, so its fast lane and webhook are off
while its valid stricter parts, other windows included, still apply. In config.json, an invalid
window is never dropped: until you fix it, every command that is not read-only asks, while the
rules keep running (a rule deny stays a deny). reflex doctor names the window and the problem.
Seeing it. reflex status prints Change freeze: ACTIVE now: ... or none active, with the
number of windows, for config.json and the team policy of the directory it runs in.
reflex check "kubectl apply -f app.yaml" --cwd ~/infra/envs/prod shows the decision a frozen
command would get. reflex audit --prod-only lists what happened during a freeze.
Limits. The window is read from the machine's clock. You can edit your own config.json or
turn Reflex off (--mode off, where no freeze applies); a freeze is a control on the agent, not on
you. Production is read from the command and its context, not from the scripts it runs: a
./deploy.sh whose only production reference is inside the script is not frozen by a prod window
(use "applies_to": "all" for a full stop). A team policy's
windows are protected like the rest of the file: an agent shell command that writes .reflex/ gets a
tamper ask, and code review protects the committed file. The production markers read text, so a
command that names production in an argument (gh pr create --title "prod fix") counts, which only
adds asks. Reflex gates shell commands; file edits and MCP calls are not frozen.