Refuse a push that is not formatted

2c26298299a9 · AtlantisPleb · · parent 5fa104703356

Refuse a push that is not formatted

An unformatted file reaches main silently, because mix precommit runs
mix format rather than checking it and the installed push guard only checks
where a push is going. It surfaces much later and much worse: every release
gate creates a fresh worktree, mix format rewrites the file mid-run, and the
relup stage then fails for a dirty worktree — reporting a whitespace slip as
a deploy blocker, three stages and forty minutes from the cause. That has
now happened twice today, on two different files.

The guard checks formatting before the push, which costs seconds. Proven
both ways: an unformatted tree exits 1 naming the files, a formatted tree
exits 0. Also formats the two files currently unformatted on main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnhfrafYx5ZGaMbzZEJQ2d
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

pushed
by user · WAL seq 401 · 2026-08-25T15:28:22.687745Z

Changed files

  • modified lib/openagents_web/controllers/responses_controller.ex
  • modified ops/dev/install-push-guard.sh
  • modified test/openagents_web/controllers/responses_controller_test.exs

Diff

3 files changed, +26 -4

lib/openagents_web/controllers/responses_controller.ex modified +4 -1

@@ -265,7 +265,10 @@ defmodule OpenAgentsWeb.ResponsesController do

265 265
266 266
  defp collect(conn, model, request) do
267 267
    parent = self()
268
    result = model.adapter.stream(request, fn event -> send(parent, {:responses_event, event}) end)
268
269
    result =
270
      model.adapter.stream(request, fn event -> send(parent, {:responses_event, event}) end)
271
269 272
    events = drain([])
270 273
271 274
    text =
ops/dev/install-push-guard.sh modified +17 -2

@@ -1,7 +1,8 @@

1 1
#!/bin/sh
2 2
set -eu
3 3
4
# Installs the forge-only push guard into this clone.
4
# Installs the push guard into this clone: where a push is going, and whether
5
# what it carries is formatted.
5 6
#
6 7
# `ops/ci/push-remote-check.sh` is the guard itself, and `.githooks/pre-push`
7 8
# already runs it -- but `.githooks` binds only where `core.hooksPath` points

@@ -67,7 +68,21 @@ cat >"$hook_path" <<'HOOK'

67 68
set -eu
68 69
69 70
common_dir=$(cd "$(git rev-parse --git-common-dir)" && pwd)
70
exec "$common_dir/hooks/openagents-push-remote-check.sh" "$@"
71
"$common_dir/hooks/openagents-push-remote-check.sh" "$@"
72
73
# Formatting, because `mix precommit` runs `mix format` rather than checking
74
# it. An unformatted file therefore reaches main silently, and then every
75
# release gate rewrites it mid-run and fails the relup stage for a dirty
76
# worktree — reporting a whitespace slip as a deploy blocker, three stages
77
# and forty minutes away from the cause. Seconds here, against that.
78
repo_root=$(git rev-parse --show-toplevel)
79
if [ -f "$repo_root/mix.exs" ] && command -v mix >/dev/null 2>&1; then
80
  if ! (cd "$repo_root" && mix format --check-formatted >/dev/null 2>&1); then
81
    echo "Refusing the push: files are not formatted. Run 'mix format'." >&2
82
    (cd "$repo_root" && mix format --check-formatted 2>&1 | sed -n '1,20p') >&2
83
    exit 1
84
  fi
85
fi
71 86
HOOK
72 87
73 88
chmod +x "$hook_path"
test/openagents_web/controllers/responses_controller_test.exs modified +5 -1

@@ -1,7 +1,11 @@

1 1
defmodule OpenAgentsWeb.ResponsesControllerTest do
2 2
  use OpenAgentsWeb.ConnCase, async: false
3 3
4
  alias OpenAgents.Providers.{FailingTestProvider, RecordingTestProvider, UnconfiguredTestProvider}
4
  alias OpenAgents.Providers.{
5
    FailingTestProvider,
6
    RecordingTestProvider,
7
    UnconfiguredTestProvider
8
  }
5 9
6 10
  # The default model rides the Vercel gateway lane; swapping the lane's
7 11
  # adapter is how a test decides what "real inference" answers with.

This page updates live while a promote is in flight · changelog