Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 11, 2025

Description

The action was not failing workflows when linting failed due to two bugs: (1) exit codes were never checked after linting steps, and (2) $? captured tee's exit code instead of the linting tool's.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Workflow/CI improvement
  • Dependency update

Related Issue

Fixes #(issue number from problem statement)

Changes Made

Core fixes:

  • Changed exit code capture from $? to ${PIPESTATUS[0]} in all linting steps (pylint, black, mypy)
  • Added final validation step that checks all exit codes and fails with exit 1 if any tool failed
  • Step runs with if: always() to ensure it executes after badge/README updates

Documentation:

  • Added "Behavior" section to README explaining failure handling semantics

Testing:

  • Added test-failure-behavior job that verifies action fails correctly and badges still generate

Technical detail:

# Before - incorrect, captures tee's exit code (always 0)
pylint ... | tee output.txt
echo "PYLINT_EXIT_CODE=$?" >> $GITHUB_ENV

# After - correct, captures pylint's exit code
pylint ... | tee output.txt
echo "PYLINT_EXIT_CODE=${PIPESTATUS[0]}" >> $GITHUB_ENV

The final check step:

- name: Check linting results and fail if needed
  run: |
    FAILED=0
    [ "${PYLINT_EXIT_CODE:-0}" != "0" ] && FAILED=1
    [ "${BLACK_EXIT_CODE:-0}" != "0" ] && FAILED=1
    [ "${MYPY_EXIT_CODE:-0}" != "0" ] && FAILED=1
    [ "$FAILED" == "1" ] && exit 1
  if: always()

Changelog

  • I have updated CHANGELOG.md under the [Unreleased] section
  • No changelog update needed (documentation/CI only)

Testing

  • All existing tests pass
  • I have added tests for my changes (if applicable)
  • I have tested the changes locally
  • Workflow checks pass

Documentation

  • I have updated relevant documentation
  • No documentation updates needed

Security

  • My changes don't introduce security vulnerabilities
  • I have reviewed security scan results
  • No security concerns

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code where necessary
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

Additional Notes

Badge generation and README updates already had if: always() so they continue to run regardless of linting results. The fix ensures the action still fails the workflow after these steps complete, maintaining proper CI/CD semantics while preserving accurate status reporting.

Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug]: Action does not fail on lint error and badges/README are not updated</issue_title>
<issue_description>### Bug Description

The python-linting action does not fail the whole workflow when any step in the linting process fails. Furthermore, when a lint step fails, the badge and README.md are not updated or committed as expected. Both behaviors need to be fixed to maintain accurate status reporting and badge integrity.

Steps to Reproduce

  1. Configure the python-linting GitHub Action with any linting tool (e.g., flake8, black).
  2. Ensure one step is configured to cause a lint failure (e.g., a source file with a lint error).
  3. Run the workflow.
  4. Observe that the action does not fail the whole workflow and badges/README.md are not always updated/committed.

Expected Behavior

  • If any linting step fails, the whole GitHub Action should fail (return a non-zero exit code and stop the workflow, as per standard CI behavior).
  • Badge generation and README.md update should always run, regardless of previous step success or failure.
  • README.md and badges should be committed even if linting fails.
  • Documentation should clearly describe the above behaviors.

Actual Behavior

  • Linting steps can fail, but the action continues and does not report the whole workflow as failed.
  • When a lint step fails, badge generation and README.md updates are skipped or not committed.
  • Status badges and documentation are not always in sync with actual CI results after failures.

Action Version

v0.0.8

Python Version

3.10

Runner OS

ubuntu-latest

Workflow Configuration

- name: Python Linting
   id: linting-python
   uses: thoughtparametersllc/python-linting@v0.0.8
   with:
     python-version: "3.10"
     requirements-file: "dev-requirements.txt"
     pylint_options: "--max-line-length=120"
     black_options: "--line-length 120"
     mypy_options: "--strict"
     generate-badges: 'true'
     badges-directory: '.github/badges'
     update-readme: 'true'
     readme-path: 'README.md'
     badge-style: 'path'

Relevant Logs

Run thoughtparametersllc/python-linting@v0.0.8
  with:
    python-version: 3.10
    requirements-file: dev-requirements.txt
    pylint_options: --max-line-length=120
    black_options: --line-length 120
    mypy_options: --strict
    generate-badges: true
    badges-directory: .github/badges
    update-readme: true
    readme-path: README.md
    badge-style: path
Run actions/setup-python@v6
Installed versions
Run pip3 install pylint black mypy
Collecting pylint
  Downloading pylint-4.0.2-py3-none-any.whl.metadata (12 kB)
Collecting black
  Downloading black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (85 kB)
Collecting mypy
  Downloading mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.2 kB)
Collecting astroid<=4.1.dev0,>=4.0.1 (from pylint)
  Downloading astroid-4.0.2-py3-none-any.whl.metadata (4.4 kB)
Collecting dill>=0.2 (from pylint)
  Downloading dill-0.4.0-py3-none-any.whl.metadata (10 kB)
Collecting isort!=5.13,<8,>=5 (from pylint)
  Downloading isort-7.0.0-py3-none-any.whl.metadata (11 kB)
Collecting mccabe<0.8,>=0.6 (from pylint)
  Downloading mccabe-0.7.0-py2.py3-none-any.whl.metadata (5.0 kB)
Collecting platformdirs>=2.2 (from pylint)
  Downloading platformdirs-4.5.0-py3-none-any.whl.metadata (12 kB)
Collecting tomli>=1.1 (from pylint)
  Downloading tomli-2.3.0-py3-none-any.whl.metadata (10 kB)
Collecting tomlkit>=0.10.1 (from pylint)
  Downloading tomlkit-0.13.3-py3-none-any.whl.metadata (2.8 kB)
Collecting typing-extensions>=4 (from astroid<=4.1.dev0,>=4.0.1->pylint)
  Downloading typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
Collecting click>=8.0.0 (from black)
  Downloading click-8.3.0-py3-none-any.whl.metadata (2.6 kB)
Collecting mypy-extensions>=0.4.3 (from black)
  Downloading mypy_extensions-1.1.0-py3-none-any.whl.metadata (1.1 kB)
Collecting packaging>=22.0 (from black)
  Downloading packaging-25.0-py3-none-any.whl.metadata (3.3 kB)
Collecting pathspec>=0.9.0 (from black)
  Downloading pathspec-0.12.1-py3-none-any.whl.metadata (21 kB)
Collecting pytokens>=0.3.0 (from black)
  Downloading pytokens-0.3.0-py3-none-any.whl.metadata (2.0 kB)
Downloading pylint-4.0.2-py3-none-any.whl (536 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 536.0/536.0 kB 56.5 MB/s  0:00:00
Downloading astroid-4.0.2-py3-none-any.whl (276 kB)
Downloading isort-7.0.0-py3-none-any.whl (94 kB)
Downloading mccabe-0.7.0-py2.py3-none-any.whl (7.3 kB)
Downloading black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (1.7 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 39.1 MB/s  0:00:00
Downloading mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (13.2 MB)
   ━━━━━...

</details>

- Fixes thoughtparametersllc/python-linting#12

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for you](https://github.com/thoughtparametersllc/python-linting/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: thoughtparametersllc <194255310+thoughtparametersllc@users.noreply.github.com>
Co-authored-by: thoughtparametersllc <194255310+thoughtparametersllc@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix action to fail on lint errors and update badges Fix action failure behavior and exit code capture Nov 11, 2025
@thoughtparametersllc thoughtparametersllc marked this pull request as ready for review November 11, 2025 01:43
Copilot AI review requested due to automatic review settings November 11, 2025 01:43
Copy link
Owner

@thoughtparametersllc thoughtparametersllc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failure condition doesn't work correctly here is the log. Please correct and submit your changes.

Prepare all required actions
Getting action download info
Download action repository 'actions/setup-python@v6' (SHA:e797f83bcb11b83ae66e0230d6156d7c80228e7c)
Run ./
Run actions/setup-python@v6
Installed versions
Run pip3 install pylint black mypy
Collecting pylint
Downloading pylint-4.0.2-py3-none-any.whl.metadata (12 kB)
Collecting black
Downloading black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (85 kB)
Collecting mypy
Downloading mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.2 kB)
Collecting astroid<=4.1.dev0,>=4.0.1 (from pylint)
Downloading astroid-4.0.2-py3-none-any.whl.metadata (4.4 kB)
Collecting dill>=0.3.6 (from pylint)
Downloading dill-0.4.0-py3-none-any.whl.metadata (10 kB)
Collecting isort!=5.13,<8,>=5 (from pylint)
Downloading isort-7.0.0-py3-none-any.whl.metadata (11 kB)
Collecting mccabe<0.8,>=0.6 (from pylint)
Downloading mccabe-0.7.0-py2.py3-none-any.whl.metadata (5.0 kB)
Collecting platformdirs>=2.2 (from pylint)
Downloading platformdirs-4.5.0-py3-none-any.whl.metadata (12 kB)
Collecting tomlkit>=0.10.1 (from pylint)
Downloading tomlkit-0.13.3-py3-none-any.whl.metadata (2.8 kB)
Collecting click>=8.0.0 (from black)
Downloading click-8.3.0-py3-none-any.whl.metadata (2.6 kB)
Collecting mypy-extensions>=0.4.3 (from black)
Downloading mypy_extensions-1.1.0-py3-none-any.whl.metadata (1.1 kB)
Collecting packaging>=22.0 (from black)
Downloading packaging-25.0-py3-none-any.whl.metadata (3.3 kB)
Collecting pathspec>=0.9.0 (from black)
Downloading pathspec-0.12.1-py3-none-any.whl.metadata (21 kB)
Collecting pytokens>=0.3.0 (from black)
Downloading pytokens-0.3.0-py3-none-any.whl.metadata (2.0 kB)
Collecting typing_extensions>=4.6.0 (from mypy)
Downloading typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
Downloading pylint-4.0.2-py3-none-any.whl (536 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 536.0/536.0 kB 5.4 MB/s 0:00:00
Downloading astroid-4.0.2-py3-none-any.whl (276 kB)
Downloading isort-7.0.0-py3-none-any.whl (94 kB)
Downloading mccabe-0.7.0-py2.py3-none-any.whl (7.3 kB)
Downloading black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (1.6 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 9.3 MB/s 0:00:00
Downloading mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (13.2 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13.2/13.2 MB 24.9 MB/s 0:00:00
Downloading click-8.3.0-py3-none-any.whl (107 kB)
Downloading dill-0.4.0-py3-none-any.whl (119 kB)
Downloading mypy_extensions-1.1.0-py3-none-any.whl (5.0 kB)
Downloading packaging-25.0-py3-none-any.whl (66 kB)
Downloading pathspec-0.12.1-py3-none-any.whl (31 kB)
Downloading platformdirs-4.5.0-py3-none-any.whl (18 kB)
Downloading pytokens-0.3.0-py3-none-any.whl (12 kB)
Downloading tomlkit-0.13.3-py3-none-any.whl (38 kB)
Downloading typing_extensions-4.15.0-py3-none-any.whl (44 kB)
Installing collected packages: typing_extensions, tomlkit, pytokens, platformdirs, pathspec, packaging, mypy-extensions, mccabe, isort, dill, click, astroid, pylint, mypy, black

Successfully installed astroid-4.0.2 black-25.11.0 click-8.3.0 dill-0.4.0 isort-7.0.0 mccabe-0.7.0 mypy-1.18.2 mypy-extensions-1.1.0 packaging-25.0 pathspec-0.12.1 platformdirs-4.5.0 pylint-4.0.2 pytokens-0.3.0 tomlkit-0.13.3 typing_extensions-4.15.0
Run pylint --reports=y --jobs=0 . 2>&1 | tee pylint_output.txt
************* Module update_badges
update_badges.py:47:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:55:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:76:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:80:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:83:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:93:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:101:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:110:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:137:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:141:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:145:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:151:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:155:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:159:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:163:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:197:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:199:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:205:0: C0301: Line too long (113/100) (line-too-long)
update_badges.py:207:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:215:0: C0303: Trailing whitespace (trailing-whitespace)
update_badges.py:28:4: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return)
update_badges.py:84:4: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return)
update_badges.py:119:4: W0613: Unused argument 'badge_position' (unused-argument)
update_badges.py:11:0: W0611: Unused import re (unused-import)
************* Module test_project.bad_code
test_project/bad_code.py:3:0: C0116: Missing function or method docstring (missing-function-docstring)

Report

90 statements analysed.

Statistics by type

+---------+-------+-----------+-----------+------------+---------+
|type |number |old number |difference |%documented |%badname |
+=========+=======+===========+===========+============+=========+
|module |2 |NC |NC |100.00 |0.00 |
+---------+-------+-----------+-----------+------------+---------+
|class |0 |NC |NC |0 |0 |
+---------+-------+-----------+-----------+------------+---------+
|method |0 |NC |NC |0 |0 |
+---------+-------+-----------+-----------+------------+---------+
|function |6 |NC |NC |83.33 |0.00 |
+---------+-------+-----------+-----------+------------+---------+

233 lines have been analyzed

Raw metrics

+----------+-------+------+---------+-----------+
|type |number |% |previous |difference |
+==========+=======+======+=========+===========+
|code |128 |54.94 |NC |NC |
+----------+-------+------+---------+-----------+
|docstring |54 |23.18 |NC |NC |
+----------+-------+------+---------+-----------+
|comment |17 |7.30 |NC |NC |
+----------+-------+------+---------+-----------+
|empty |34 |14.59 |NC |NC |
+----------+-------+------+---------+-----------+

Duplication

+-------------------------+------+---------+-----------+
| |now |previous |difference |
+=========================+======+=========+===========+
|nb duplicated lines |0 |NC |NC |
+-------------------------+------+---------+-----------+
|percent duplicated lines |0.000 |NC |NC |
+-------------------------+------+---------+-----------+

Messages by category

+-----------+-------+---------+-----------+
|type |number |previous |difference |
+===========+=======+=========+===========+
|convention |21 |NC |NC |
+-----------+-------+---------+-----------+
|refactor |2 |NC |NC |
+-----------+-------+---------+-----------+
|warning |2 |NC |NC |
+-----------+-------+---------+-----------+
|error |0 |NC |NC |
+-----------+-------+---------+-----------+

% errors / warnings by module

+----------------------+------+--------+---------+-----------+
|module |error |warning |refactor |convention |
+======================+======+========+=========+===========+
|update_badges |0.00 |100.00 |100.00 |95.24 |
+----------------------+------+--------+---------+-----------+
|test_project.bad_code |0.00 |0.00 |0.00 |4.76 |
+----------------------+------+--------+---------+-----------+

Messages

+---------------------------+------------+
|message id |occurrences |
+===========================+============+
|trailing-whitespace |19 |
+---------------------------+------------+
|no-else-return |2 |
+---------------------------+------------+
|unused-import |1 |
+---------------------------+------------+
|unused-argument |1 |
+---------------------------+------------+
|missing-function-docstring |1 |
+---------------------------+------------+
|line-too-long |1 |
+---------------------------+------------+


Your code has been rated at 7.22/10

Error: Process completed with exit code 28.
Run echo "## Pylint 🎉" >> $GITHUB_STEP_SUMMARY
Run black --check --check . 2>&1 | tee black_output.txt
would reformat /home/runner/work/python-linting/python-linting/test_project/bad_code.py
would reformat /home/runner/work/python-linting/python-linting/update_badges.py

Oh no! 💥 💔 💥
2 files would be reformatted.
Error: Process completed with exit code 1.
Run echo "## Black 🎉" >> $GITHUB_STEP_SUMMARY
Run mypy . 2>&1 | tee mypy_output.txt
Success: no issues found in 2 source files
Run echo "## MyPy 🎉" >> $GITHUB_STEP_SUMMARY
Run if [ "true" == "true" ]; then
✓ Generated SVG badges in .github/test-failure-badges
Run if [ "false" == "true" ]; then
Run if [ "true" == "true" ] || [ "false" == "true" ]; then
[detached HEAD bd8ce62] Update linting badges [skip ci]
3 files changed, 60 insertions(+)
create mode 100644 .github/test-failure-badges/black.svg
create mode 100644 .github/test-failure-badges/mypy.svg
create mode 100644 .github/test-failure-badges/pylint.svg
fatal: You are not currently on a branch.
To push the history leading to the current (detached HEAD)
state now, use

git push origin HEAD:<name-of-remote-branch>

⚠ Warning: Failed to push changes. This may be due to insufficient permissions or a merge conflict.
Please ensure the workflow has write permissions to the repository.
Run echo "Checking linting results..."
Checking linting results...
✓ Pylint passed
✓ Black passed
✓ MyPy passed

✅ All linting checks passed!
0s
Run if [ "success" != "failure" ]; then
if [ "success" != "failure" ]; then
echo "❌ Error: Action should have failed but didn't"
echo "Outcome was: success"
exit 1
fi
echo "✓ Action correctly failed when linting failed"
shell: /usr/bin/bash -e {0}
env:
pythonLocation: /opt/hostedtoolcache/Python/3.11.14/x64
PKG_CONFIG_PATH: /opt/hostedtoolcache/Python/3.11.14/x64/lib/pkgconfig
Python_ROOT_DIR: /opt/hostedtoolcache/Python/3.11.14/x64
Python2_ROOT_DIR: /opt/hostedtoolcache/Python/3.11.14/x64
Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.11.14/x64
LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.11.14/x64/lib
MYPY_EXIT_CODE: 0
❌ Error: Action should have failed but didn't
Outcome was: success
Error: Process completed with exit code 1.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a critical bug where the python-linting action was not failing workflows when linting tools reported errors. The root cause was twofold: exit codes were captured from tee instead of the linting tools using $?, and there was no final validation step to fail the action based on those exit codes.

Key changes:

  • Changed exit code capture from $? to ${PIPESTATUS[0]} for pylint, black, and mypy to correctly capture the linting tool's exit code instead of tee's
  • Added a final validation step that checks all exit codes and fails with exit 1 if any linting tool failed
  • Added "Behavior" section to README documenting the failure handling semantics
  • Added test-failure-behavior job to verify the action fails correctly while still generating badges

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
action.yml Fixed exit code capture using ${PIPESTATUS[0]} for all three linting tools and added final validation step to fail the action if any tool failed
README.md Added documentation explaining the action's failure handling behavior and workflow execution semantics
.github/workflows/test-action.yml Added comprehensive test job to verify the action fails when linting fails and badges are still generated

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@thoughtparametersllc thoughtparametersllc deleted the copilot/fix-lint-action-failure branch November 11, 2025 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants