Reading GitHub Actions Logs
GitHub Actions logs can look intimidating at first.
A failed CI run may show many lines of output, but usually only one step contains the important error.
This page explains how to read CI logs slowly and find the useful information.
When to use this guide
Use this guide when:
- a pull request has a red CI check,
- a workflow failed after pushing to
main, - local checks pass but GitHub Actions fails,
- you do not know which command failed,
- the logs look too long to understand.
The most important idea
Do not read the whole log from top to bottom immediately.
Start by finding:
Which job failed?
Which step failed?
What was the first useful error message?
The first useful error is usually more important than the last line of the log.
Where to find CI logs
When a pull request has a failing check:
- Open the pull request on GitHub.
- Scroll to the checks section.
- Click the failed check.
- Open the failed job.
- Open the failed step.
In this project, the job is called:
Quality checks
The workflow is called:
CI
Understand the workflow structure
This project uses a workflow similar to:
name: CI
jobs:
quality:
name: Quality checks
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- name: Install uv
- name: Set up Python
- name: Install dependencies
- name: Run Ruff linting
- name: Check formatting
- name: Lint Markdown
- name: Build documentation site
- name: Run tests with coverage
- name: Run type checking
- name: Build distributions
When CI fails, one of these steps usually failed.
Do not debug everything at once.
Start with the failed step.
The separate Documentation workflow runs after relevant changes reach main.
It has two jobs:
Build documentation
Deploy documentation
The build job creates and uploads the site artifact. The deploy job publishes
that artifact to the github-pages environment.
Step: Checkout repository
What it does
This step downloads the repository files into the GitHub Actions runner.
Example:
- name: Checkout repository
uses: actions/checkout@v6
If it fails
This is uncommon.
Possible causes include:
- GitHub service issue,
- invalid workflow syntax,
- repository access problem.
Usually, this is not caused by Python code.
Step: Install uv
What it does
This step installs uv in the CI environment.
Example:
- name: Install uv
uses: astral-sh/setup-uv@v8.1.0
If it fails
Possible causes include:
- wrong action name,
- invalid action version,
- temporary network issue,
- GitHub Actions service issue.
Check whether the uses: line is correct.
Step: Set up Python
What it does
This step installs the Python version requested by the project.
Example:
- name: Set up Python
run: uv python install
The project uses:
.python-version
to define the selected Python version.
If it fails
Possible causes include:
- invalid Python version in
.python-version, - Python version not available,
uvwas not installed correctly in the previous step.
Check:
.python-version
For this guide, it should contain something like:
3.12
Step: Install dependencies
What it does
This step installs project dependencies in CI.
Example:
- name: Install dependencies
run: uv sync --locked --group dev
CI uses:
uv sync --locked --group dev
because it should verify the committed lockfile, not silently update it.
If it fails
A common cause is that pyproject.toml and uv.lock are out of sync.
This may happen after adding or removing a dependency without committing the updated lockfile.
How to fix it
Run locally:
uv sync
or:
uv lock
Then check:
git status
If uv.lock changed, commit it:
git add pyproject.toml uv.lock
git commit -m "chore: update dependency lockfile"
Then push again.
Step: Run Ruff linting
What it does
This step runs:
uv run ruff check .
It checks the project for selected linting issues.
If it fails
The log usually shows:
- file path,
- line number,
- rule code,
- message.
Example:
src/example_project/module.py:1:1: F401 `os` imported but unused
This means:
file: src/example_project/module.py
line: 1
column: 1
rule: F401
problem: os was imported but not used
How to fix it
Open the file and fix the issue.
Some Ruff issues can be fixed automatically:
uv run ruff check . --fix
Then review the diff:
git diff
Do not apply fixes blindly without reading the changes.
After fixing, run:
uv run ruff check .
uv run ruff format --check .
uv run rumdl check .
uv run mkdocs build --strict
uv run pytest --cov=text_toolkit --cov-report=term-missing
uv run mypy
uv build --no-sources
Step: Check formatting
What it does
This step runs:
uv run ruff format --check .
It checks whether files are formatted correctly.
It does not modify files in CI.
If it fails
The log may say that files would be reformatted.
That means formatting is different from Ruff's expected format.
How to fix it
Run locally:
uv run ruff format .
Then run all checks again:
uv run ruff check .
uv run ruff format --check .
uv run rumdl check .
uv run mkdocs build --strict
uv run pytest --cov=text_toolkit --cov-report=term-missing
uv run mypy
Commit the formatting changes:
git add .
git commit -m "style: format files"
If the formatting change is part of another small documentation or code PR, it may be better to amend the existing commit instead of adding a separate formatting-only commit.
Step: Lint Markdown
What it does
This step runs:
uv run rumdl check .
It checks Markdown structure and consistency using the project configuration.
If it fails
The log shows the file, line, rule identifier, and a short explanation. Read the rule before changing the document; some project-specific patterns may need a narrow configuration exception.
How to fix it
Run the check locally:
uv run rumdl check .
Some issues can be fixed with uv run rumdl check --fix ., but always review
the resulting diff before committing it.
Step: Build documentation site
What it does
This step runs:
uv run mkdocs build --strict
It renders the documentation site and treats warnings about navigation, links, anchors, or configuration as failures.
If it fails
Read the first warning in the log. Later messages may be consequences of the same missing page, invalid navigation entry, or broken link.
How to fix it
Run the strict build locally:
uv run mkdocs build --strict
For visual review, run uv run mkdocs serve and open the local address shown in
the terminal.
Step: Run tests with coverage
What it does
This step runs:
uv run pytest --cov=text_toolkit --cov-report=term-missing
It checks whether the code behaves as expected and reports statement and branch
coverage for text_toolkit. There is no minimum percentage gate.
If it fails
Pytest usually shows:
- failing test file,
- failing test name,
- assertion error,
- expected value,
- actual value.
Example:
FAILED tests/test_text_stats.py::test_count_characters_includes_whitespace_by_default
This tells you which test failed.
How to fix it
Start by reading:
test file
test name
assertion error
Then run tests locally:
uv run pytest --cov=text_toolkit --cov-report=term-missing
For more detail:
uv run pytest -v
To run one test file:
uv run pytest tests/test_text_stats.py
Fix the code or the test depending on what is actually wrong.
Then run:
uv run ruff check .
uv run ruff format --check .
uv run rumdl check .
uv run mkdocs build --strict
uv run pytest --cov=text_toolkit --cov-report=term-missing
uv run mypy
Step: Run type checking
What it does
This step runs:
uv run mypy
mypy checks whether code in src/ and tests/ follows the project's type
annotations and strict configuration.
If it fails
The message usually includes:
- a file path,
- a line number,
- an explanation,
- an error code in square brackets.
How to fix it
Run the same command locally:
uv run mypy
Fix the code or its annotation. Avoid broad ignores that hide unrelated type errors. Then run the full local checks again.
Step: Build distributions
What it does
This step runs:
uv build --no-sources
It builds a wheel and source distribution from the project's standard metadata.
If it fails
Start with the first build backend error. Common causes include:
- invalid project metadata,
- a missing package directory,
- a package name that does not match the source layout,
- a required file missing from the source distribution.
Run the same command locally:
uv build --no-sources
The generated dist/ directory is ignored by Git and should not be committed.
Local checks pass, but CI fails
This can happen.
Common causes:
- uncommitted local files,
- missing
uv.lockupdate, - different Python version,
- branch is stale,
- workflow file changed,
- platform-specific behavior.
Start locally:
git status
uv sync
uv run ruff check .
uv run ruff format --check .
uv run rumdl check .
uv run mkdocs build --strict
uv run pytest --cov=text_toolkit --cov-report=term-missing
uv run mypy
uv build --no-sources
If git status shows changes, check whether they should be committed.
If uv.lock changed, commit it.
If local checks still pass, inspect the failed CI step carefully.
CI fails on main after merge
If CI fails on main, do not ignore it.
The main branch should stay healthy.
Recommended response:
- Open the failed workflow run.
- Identify the failed job and step.
- Read the first useful error.
- Reproduce locally if possible.
- Create a small fix branch.
- Open a pull request with the fix.
Example branch:
git switch main
git pull
git switch -c fix/ci-failure
Example commit:
git commit -m "fix: resolve CI failure"
How to read a long log
Long logs are easier to read if you search for keywords.
Useful words to search for:
error
failed
FAILED
Traceback
ModuleNotFoundError
AssertionError
would be reformatted
out of date
But be careful.
Some logs contain warnings or internal messages that are not the actual cause.
Focus on the failed step first.
First error vs last error
The last line of a log may only say:
Error: Process completed with exit code 1.
That line tells you that the command failed.
It does not explain why.
The useful error is usually above it.
Look for the first message that explains the real problem.
Do not fix everything at once
When CI fails, avoid changing many unrelated things.
Bad approach:
format files, update dependencies, rewrite tests, change CI, and refactor code
Better approach:
fix the one issue that caused CI to fail
Then run checks and push again.
Small fixes are easier to review.
Useful local commands
Check repository state:
git status
Review unstaged changes:
git diff
Review staged changes:
git diff --staged
Sync dependencies:
uv sync
Run quality checks:
uv run ruff check .
uv run ruff format --check .
uv run rumdl check .
uv run mkdocs build --strict
uv run pytest --cov=text_toolkit --cov-report=term-missing
uv run mypy
Format files:
uv run ruff format .
Run tests with more detail:
uv run pytest -v
Common CI failure examples
Lockfile is stale
Symptom:
uv sync --locked --group dev
fails.
Likely fix:
uv lock
git add pyproject.toml uv.lock
git commit -m "chore: update lockfile"
Formatting failed
Symptom:
would be reformatted
Likely fix:
uv run ruff format .
git add .
git commit -m "style: format files"
Unused import
Symptom:
F401 imported but unused
Likely fix:
Remove the unused import.
Test assertion failed
Symptom:
AssertionError
Likely fix:
Check whether the implementation or the test expectation is wrong.
Package import failed
Symptom:
ModuleNotFoundError
Likely fix:
Check the project structure and build system:
[build-system]
requires = ["uv_build>=0.12.19,<0.13"]
build-backend = "uv_build"
Then confirm that the dependency installation step completed successfully.
What green CI means
A green CI run means:
- dependencies installed,
- Ruff linting passed,
- formatting check passed,
- Markdown linting passed,
- documentation site build passed,
- tests and coverage reporting passed,
- type checking passed,
- wheel and source distribution builds passed.
It does not mean:
- the design is perfect,
- the documentation is clear,
- the change is useful,
- the pull request should be merged without review.
CI checks repetitive things.
Humans still review meaning and quality.
What a green documentation deployment means
A green Documentation workflow means:
- the site was built strictly from locked dependencies,
- the generated
site/directory was uploaded as an artifact, - GitHub Pages accepted the deployment,
- GitHub reported a public deployment URL.
It does not prove that every page looks or reads as intended. Open the published site and inspect the changed page after deployment.
If Build documentation fails, reproduce the strict build locally. If Deploy
documentation fails, inspect Pages settings, workflow permissions, the
github-pages environment, and the first useful deployment error.
Rule of thumb
When CI fails, ask:
Which step failed?
What command did that step run?
Can I run the same command locally?
What is the first useful error message?
Do not panic.
CI is not judging you.
It is giving feedback.