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:

  1. Open the pull request on GitHub.
  2. Scroll to the checks section.
  3. Click the failed check.
  4. Open the failed job.
  5. 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,
  • uv was 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.lock update,
  • 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:

  1. Open the failed workflow run.
  2. Identify the failed job and step.
  3. Read the first useful error.
  4. Reproduce locally if possible.
  5. Create a small fix branch.
  6. 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.