Skip to content

Flake: secret_forms_live expiry label races the sweeper test's global :secret_expiry_notice_days #1932

Description

@jhgaylor

apps/fountain/test/fountain_web/live/secret_forms_live_test.exs:188 fails intermittently, most recently in a merge queue build where it ejected an unrelated PR (#1872, the sandbox capacity queue, which touches nothing in vaults or secrets).

The failing assertion

test "add_secret records an optional expiry date and renders its status"
test/fountain_web/live/secret_forms_live_test.exs:188

    assert html =~ "expires in 3d"
    right: "expires in 3d"
    left:  "<div class=..."   # the rendered page, without that label

How often, out of what

One merge-queue build of six for #1872 so far. Run: https://github.com/managoat/fountain/actions/runs/34595416749 (partition 2, 2026-09-11 11:47 UTC). The two earlier #1872 queue failures were a different, real problem (an interaction with #1840) and are fixed; this one is not that.

The mechanism

FountainWeb.VaultsLive.Form.expiry_status/1 renders the label only inside its notice window:

days = Date.diff(DateTime.to_date(expires_at), Date.utc_today())
cond do
  ...
  days <= SecretExpirySweeper.notice_days() -> {:expiring, "expires in #{days}d"}
  true -> {:ok, "expires #{DateTime.to_date(expires_at)}"}
end

SecretExpirySweeper.notice_days/0 is Application.get_env(:fountain, :secret_expiry_notice_days, 7) — global.

apps/fountain/test/fountain/workers/secret_expiry_sweeper_test.exs is use Fountain.DataCase, async: true and at line 120 does:

Application.put_env(:fountain, :secret_expiry_notice_days, 0)

While that write is held, 3 <= 0 is false for every concurrent test, so the label falls to the true -> branch and the assertion fails. Nothing is wrong with either test in isolation; they only have to overlap. Note the date arithmetic is Date.diff on dates, so this is not a time-of-day boundary — it is the global write.

Why the guardrail did not catch it

apps/fountain/test/fountain/async_global_config_guardrail_test.exs exists for exactly this class and allowlists this file:

# :secret_expiry_notice_days — read only by the sweeper under test.
"apps/fountain/test/fountain/workers/secret_expiry_sweeper_test.exs",

That justification is no longer true: the key is also read by expiry_status/1 on every vault form render, and secret_forms_live_test.exs asserts on the result. The guardrail's own docstring gives the fix: "The allowlist is not a blessing... Moving one into an async: false module is always correct, and is what to do the moment one of them is suspected in a flake."

Suggested fix

Move the put_env portion of secret_expiry_sweeper_test.exs into a sibling async: false module and delete its allowlist entry, the way quotas_test.exs and user_emails_test.exs were handled. Worth re-reading the other allowlist entries with the same question — each says "read only by X", and this one shows how that goes stale when a reader is added later.

Filed rather than fixed in place because it surfaced mid-burndown of the approved PR stacks, per CLAUDE.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_016nLzDCqKwgQfuW1V5gVik4

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:testingTests, test infrastructure and CI verification.type:flakeA test fails intermittently without a relevant code change.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions