GitHub Actions GITHUB_STEP_SUMMARY too large, and the cap that decides
A GitHub Actions GITHUB_STEP_SUMMARY too large failure is the runner refusing to attach a summary file that is over a fixed one-mebibyte cap, and saying so with both numbers in kibibytes. The refusal is an annotation rather than a step result, so the usual shape of this problem is a red annotation on a green job and a run page with no summary on it.

What this error means
A summary you wrote is missing from the run page, and somewhere in the step there is a line beginning $GITHUB_STEP_SUMMARY upload aborted. The message carries two numbers: the cap and what your file actually was, both in kibibytes. The cap is 1024k and does not vary by plan, runner or repository, because it is a constant compiled into the runner. There is a quieter version of the same problem with no message at all, when the file is empty, and a third shape where the summary from an earlier step is simply not there, which is not a size problem and is not reported anywhere.
Error: $GITHUB_STEP_SUMMARY upload aborted, supports content up to a size of 1024k, got 1254k. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summaryOne constant, three outcomes
CreateStepSummaryCommand in the runner's FileCommandManager.cs declares AttachmentSizeLimit as 1024 * 1024, stats the file, and then takes one of three paths. The message you are reading is formatted from UnsupportedSummarySize in Constants.cs, with the limit and the file size both divided by 1024 before they are substituted, which is why both numbers are reported with a k.
The other two paths are silent. An empty file is skipped with nothing written to the log at all, and a file within the cap is masked for secrets, copied and queued for upload. The table is those three, in the order the code tests them.
| File size | What the runner does | What the log shows |
|---|---|---|
| Zero bytes | Skips the attachment entirely | Nothing |
| Up to 1024k | Masks secrets line by line, then queues the upload | Nothing |
| Over 1024k | Writes an error annotation and returns | The upload aborted line with both sizes |
Common causes
The summary file is genuinely over 1024k
The direct cause and the one the message states. It is normally a report, a log or a generated table appended wholesale, and it often crosses the cap on a bad day rather than every day, which is why it shows up as an intermittent missing summary rather than a consistent one. The second number in the message tells you by how much.
A tool inside the job writes the summary and does not bound it
Linters, test reporters and coverage tools frequently append to $GITHUB_STEP_SUMMARY on your behalf, so the size is decided by how many findings there were rather than by anything in your workflow. In our experience this is the version people find hardest to place, because nothing in the workflow file mentions the summary at all.
The summary is empty and was skipped without a word
A zero-byte file is skipped before the size check, with only an internal trace. If the run page shows no summary and the log shows no message either, this is the branch you are in, and the question is why the writing step produced nothing.
The append happened in a different step, or after the step ended
Each step is given a fresh file and the previous one is deleted, and the attach runs immediately after the step's handler returns. Cross-step accumulation and writes from a backgrounded process both land in a file that is never read. No message is produced, so this reads as a missing summary rather than as an error.
How to fix it
Read both numbers before changing anything
- Find the
upload abortedline and take the two sizes out of it. - The first is always 1024k; the second is your file, so the ratio tells you whether this is a trim or a rethink.
- If there is no such line and the summary is still missing, the file was empty or written outside the step, and the size is not the problem.
Move the bulk to an artifact and link it
Upload the full report as an artifact and keep the summary to the headline numbers and a link. This is the fix that survives the report growing, because the summary size stops depending on how many findings there were.
- uses: actions/upload-artifact@v4
with:
name: build-report
path: report.md
- run: |
echo "## Build report" >> "$GITHUB_STEP_SUMMARY"
echo "Full report: see the build-report artifact." >> "$GITHUB_STEP_SUMMARY"Bound the writer rather than the file
If a tool is producing the content, configure its own limit where it has one, or pipe it through something that caps the output before it reaches the file. Truncating after the fact works, but capping at the source keeps the summary readable instead of cutting it mid-table.
Write the summary in the step that produced the content
- Move every append into the step whose output it describes.
- If several steps need to contribute, have each write its own summary rather than trying to accumulate one.
- Never append from a process that outlives its step; the attachment happens as soon as the step's handler returns.
Why the job can still be green
The size branch calls context.Error(...) and returns. It does not set CommandResult, and CommandResult is the value StepsRunner merges into the step result when the step finishes. AddIssue on the execution context increments an error count, writes the line with the error tag, and queues a timeline update; it does not change the result.
So the annotation is real and visible on the run page while the step and the job carry on. That combination is what makes this one hard to notice in a busy workflow: the run is green, nothing is retried, and the only durable trace is an annotation and a missing summary.
Every step gets its own file, and the previous one is deleted
InitializeFiles runs before each step. It generates a fresh identifier, deletes the file the previous step was using, creates an empty file under the new name, and points the step_summary context, which is what $GITHUB_STEP_SUMMARY resolves to, at that new path. ProcessFiles then runs in a finally after the step's handler has completed, reads whatever is there and attaches it.
Two consequences follow, and both get mistaken for size problems. Appending to $GITHUB_STEP_SUMMARY from step A and expecting step B to add to the same document does not work, because B is writing to a different file and A's was deleted. And a background process that writes after its step has finished writes to a path nobody will read, because the attach already happened.
- name: Write the summary in the step that produced it
run: |
{
echo "## Test results"
echo ""
echo "| Suite | Result |"
echo "| --- | --- |"
echo "| unit | pass |"
} >> "$GITHUB_STEP_SUMMARY"Getting under the cap without losing the detail
The cap is on bytes, not on rendered length, so the cheapest wins are usually structural. A summary that embeds a full lint report, a diff or a test log is carrying content that reads better as a downloadable artifact anyway, and a link to that artifact costs a few dozen bytes.
Where the content genuinely belongs in the summary, truncate deliberately rather than letting the runner drop the whole thing. A summary cut to the first few hundred failures with a line saying how many were omitted is strictly better than no summary at all, which is what the cap gives you otherwise.
- name: Keep the summary inside the cap
run: |
head -c 900000 report.md >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "_Truncated. Full report is in the build-report artifact._" >> "$GITHUB_STEP_SUMMARY"Why there is no recorded run on this page
The decision this page is about is a file size compared against a compiled-in constant, and a run of ours would add one more pair of numbers to a message that already prints both of the ones that matter. The interesting parts, that the check is a hard constant rather than a quota, that the branch writes an annotation without touching the step result, and that each step is handed a new file, are all statements about the runner's source and are more precisely settled by reading it than by producing a log that restates the cap.
How to prevent it
- Treat the summary as a headline, and give anything longer to an artifact.
- Cap generated tables at a fixed number of rows and say how many were omitted.
- Check for the
upload abortedannotation in runs that are green, because nothing else will point at it. - Keep each step's summary self-contained, since the runner hands every step a fresh file.
Frequently asked questions
What is the actual size limit for a GitHub Actions job summary?
AttachmentSizeLimit as 1024 * 1024 in CreateStepSummaryCommand and reports it as 1024k because it divides both numbers by 1024 before formatting the message. It is a constant in the runner rather than a per-plan quota.Why did my job pass when the summary upload was aborted?
CommandResult, and CommandResult is what the step runner merges into the step result. The annotation is recorded and shown, and the step keeps whatever result its own process produced.Can I append to the same summary from several steps?
$GITHUB_STEP_SUMMARY at the new path, then attaches the file as soon as the step finishes. Each step writes its own summary or none.My summary is missing and there is no message at all. What happened?
finally immediately after the step's handler returns.Related guides
References
- actions/runner: FileCommandManager.cs, CreateStepSummaryCommand and the per-step file lifecycle
- actions/runner: Constants.cs, the UnsupportedSummarySize format string
- GitHub Docs: workflow commands, adding a job summary
- patrick-werner/fhir-html-validation-to-markdown#2: the aborted upload quoted here
- GitHub Actions documentation