Two issues when using mix assay --format github in umbrella projects:
The --format github emits a single line per warning:
::warning file=lib/api/admin/chat_context.ex,line=24::The @spec for create_ui_message/3 ...
The --format elixir output is much richer — code snippets, context lines, warning category — but doesn't produce GitHub annotations. You have to choose between readable output and annotations.
Dialyzer reports paths relative to each umbrella app (e.g. lib/api/admin/chat_context.ex). Assay's absolute_path/2 does Path.expand(path, project_root) which produces /repo/lib/api/admin/chat_context.ex — a file that doesn't exist. The real file is at /repo/apps/api/lib/api/admin/chat_context.ex.
This means:
- GitHub annotations point to non-existent files (no inline warnings on PRs)
format_snippet/3silently fails to read the file, so code context is missing from elixir/text output too
Change format(entries, :github, opts) to emit the ::warning annotation line followed by the same rich text block that :elixir uses.
Before:
def format(entries, :github, opts) do
project_root = Keyword.fetch!(opts, :project_root)
Enum.map(entries, fn entry ->
path = entry.relative_path || relative_display(entry.path, project_root) || "unknown"
line = entry.line || 0
message = entry |> entry_message() |> github_escape()
"::warning file=#{path},line=#{line}::#{message}"
end)
endAfter:
def format(entries, :github, opts) do
opts = Keyword.put(opts, :pretty_erlang, true)
project_root = Keyword.fetch!(opts, :project_root)
Enum.map(entries, fn entry ->
path = entry.relative_path || relative_display(entry.path, project_root) || "unknown"
line = entry.line || 0
message = entry |> entry_message() |> github_escape()
annotation = "::warning file=#{path},line=#{line}::#{message}"
body = format_text_entry(entry, project_root, opts)
annotation <> "\n" <> body
end)
endFix absolute_path/2 to check umbrella app paths when the expanded path doesn't exist. Uses Mix.Project.apps_paths/0 which returns the actual app directories regardless of naming convention (handles non-standard apps_path config).
Before:
defp absolute_path(path, root) do
case Path.type(path) do
:absolute -> path
_ -> Path.expand(path, root)
end
rescue
_ -> path
endAfter:
defp absolute_path(path, root) do
case Path.type(path) do
:absolute -> path
_ ->
expanded = Path.expand(path, root)
if File.exists?(expanded) do
expanded
else
case Mix.Project.apps_paths() do
paths when is_map(paths) ->
paths
|> Map.values()
|> Enum.find_value(fn app_path ->
candidate = Path.expand(path, Path.join(root, app_path))
if File.exists?(candidate), do: candidate
end)
|> Kernel.||(expanded)
_ ->
expanded
end
end
end
rescue
_ -> path
endOnce both changes land:
--format githubgives readable log output AND inline PR annotations- Umbrella projects get correct file paths automatically (annotations work, code snippets work)
- No external shell script needed for path rewriting
- Works with any
apps_pathconfig, not just the default"apps"