Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/elixir-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ jobs:
working-directory: elixir-mcp
strategy:
fail-fast: false
# The OTP floor is 27 (issue #97): cowlib 2.20 does not compile on OTP
# 26, and the toolchain this repo pins (mise.toml: erlang 27.0 /
# The OTP floor is 27 (issue #97), and it is measured, not assumed: on
# 2026-09-19 the OTP 26 cells (Elixir 1.15/1.16/1.17) all failed with
# `function parse/1 undefined` while this same commit's OTP 27 cell
# passed — cowlib 2.20, which cowboy requires, does not build on OTP
# 26. The toolchain this repo pins (mise.toml: erlang 27.0 /
# elixir 1.19.5-otp-27) is OTP 27. Every Elixir version named here is a
# measured cell, so the documented floor is the floor that builds.
# cell that has actually been run, so the documented floor is the floor
# that builds.
matrix:
elixir: ['1.17', '1.18', '1.19']
otp: ['27']
Expand Down
10 changes: 7 additions & 3 deletions elixir-mcp/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ mix escript.build # produces ./feedback-o-tron

Requires Elixir 1.17 or newer on OTP 27 or newer. CI tests 1.17, 1.18 and
1.19 on OTP 27, and 1.19 on OTP 27 is what this engine is developed against.
The floor was raised from OTP 26 in issue #97: `cowlib` 2.20, which `cowboy`
requires, does not compile on OTP 26, so the old floor was a promise the
build could not keep.
The floor was raised from OTP 26 in issue #97, and the reason is measured
rather than argued: on 2026-09-19 every OTP 26 cell in CI (Elixir 1.15, 1.16
and 1.17) failed to compile a dependency with `function parse/1 undefined`,
while the same commit's OTP 27 cell passed cleanly. `cowlib` 2.20, which
`cowboy` requires, is what does not build there. The old floor was a promise
the build could not keep, and on OTP 27 the identical commit builds, formats
and dialyses green.

== Run

Expand Down
31 changes: 24 additions & 7 deletions elixir-mcp/test/cli_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -252,22 +252,39 @@ defmodule FeedbackATron.CLITest do
# registered name is taken away rather than its process stopped, so the
# application supervisor has nothing to restart underneath the test.
pid = Process.whereis(FeedbackATron.Consent)

assert is_pid(pid), "the consent service must be running for this to mean anything"

Process.unregister(FeedbackATron.Consent)

on_exit(fn ->
case Process.whereis(FeedbackATron.Consent) do
nil -> Process.register(pid, FeedbackATron.Consent)
_registered -> :ok
# Best effort, and never fatal: a teardown that raises reports itself
# against whichever test happens to be running next, which is how one
# broken test hides behind an innocent one.
try do
if is_nil(Process.whereis(FeedbackATron.Consent)) and Process.alive?(pid) do
Process.register(pid, FeedbackATron.Consent)
end
rescue
_ -> :ok
end
end)

submit = fn _issue, _opts -> flunk("nothing may be sent without consent") end

{result, out} =
with_io(fn -> CLI.run(@args, confirm: fn -> true end, submit: submit) end)
# The refusal is on stderr: stdout is the MCP wire when this binary
# runs under a host, so nothing about a failure belongs on it.
{result, err} =
with_io(:stderr, fn -> CLI.run(@args, confirm: fn -> true end, submit: submit) end)

# Both values are named in the messages because which one is wrong is
# the whole diagnosis: a wrong exit code means the refusal branch was
# not taken at all, and an empty stderr means it was taken but silent.
assert result == {:halt, 4}, "must halt non-zero, got #{inspect(result)}"

assert err =~ "consent service is not running", "stderr was: #{inspect(err)}"

assert result == {:halt, 4}
assert out =~ "consent service is not running"
assert err =~ "Nothing was sent.", "stderr was: #{inspect(err)}"
end

test "a no reaches the submitter seam not at all" do
Expand Down
Loading