Keep the evidence when CI fails
The problem
The suite runs in CI and a job turns red with one line in the log. When the job ends, the files are gone, and nobody can open the trace.
The suite already wrote the trace and the reports. The job has to keep them, and one step turns them into a comment the reviewer can open.
Do it
1. Write everything to one directory
Relative output paths resolve below the test project's build output, which forces CI to search bin/**. Give CI one absolute directory instead:
1var results = Environment.GetEnvironmentVariable("PROTOTEST_RESULTS")2?? Path.Combine("TestResults", "ProtoTest");34builder5.ConfigureTracing(trace =>6trace.OutputPath = Path.Combine(results, "run.prototrace"))7.AddSink<JsonReportSink>(sink =>8sink.OutputPath = Path.Combine(results, "report.json"))9.AddSink<HtmlReportSink>(sink =>10sink.OutputPath = Path.Combine(results, "report.html"));
- Let CI name the directory
The environment variable is the job's artifact directory; a local run keeps the default below TestResults.
- Trace and reports in one place
One upload step then carries the story and the report, which is what makes the artifact link useful.
TestResults/ProtoTest/.In the workflow, set the variable once for the job: PROTOTEST_RESULTS: ${{ github.workspace }}/TestResults/ProtoTest. After a run, run.prototrace, report.json and report.html sit together in that directory.
2. Upload it even when the test step fails
- name: Keep the evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: prototest-results
path: TestResults/ProtoTest
if-no-files-found: error
The if: always() line is the one that matters. Without it the upload is skipped exactly when the trace is worth reading, because a failing test fails the step that ran it.
3. Post the digest
The evidence action uploads the trace, compares the run with the base branch's last green run, and posts what changed:
- name: ProtoTest evidence
if: always()
uses: MSeys/prototest-action@v1
with:
trace: ${{ env.PROTOTEST_RESULTS }}/run.prototrace
The comment names each test the change broke or fixed and the operation where it changed, then the failing tests with their cause and the artifact link. Each failing test also gets a check annotation at its source location. The step fails when a test that passed on the base branch fails now. The workflow needs actions: read to fetch the base branch's trace and pull-requests: write to comment. The CI page shows the whole workflow.
4. Name the build in the trace
A trace downloaded from CI should say which build made it. Name the variables once and every report carries them:
builder.ConfigureTracing(trace =>
{
trace.RunMetadataEnvironmentVariables.Add("GITHUB_RUN_ID");
trace.RunMetadataEnvironmentVariables.Add("GITHUB_SHA");
});
Each entry appears in the report's run metadata and on the run in the archive, so a trace three months old still names the commit it tested.
What happened
CI gave you one directory, one upload step and one comment. The if: always() on both steps means a failed run keeps its evidence and gets its comment. The reviewer opens the trace in the viewer without rerunning the job.
The same wiring works in every job you split the pipeline into: pull request, nightly or a smoke run against a deployed environment. Only the composition changes.
Check yourself
The test step failed. Which steps still run, and what does the reviewer open from the comment?
if: always().The upload and the post run with if: always(), so the failed run keeps its evidence and gets its comment. The comment names the tests that did not pass and links the trace artifact. The reviewer opens it in the viewer without rerunning the job.
Remember
- One output directory and one artifact step keep the trace and the reports together.
- The artifact step runs with
if: always(), because the failed run is the one worth reading. - The action posts the digest and the artifact link. The job split around it is up to you.
Go deeper
- Continuous integration: the working workflow and the action. Its three jobs are the ones to start from.
- Loop: the same archive read by an agent: fail, evidence, fix, verify, report.