pycodestyle-action: The Smallest Possible PR Reviewer for Python Style

This Docker-based GitHub Action turns `pycodestyle` output into a comment thread with shell, `jq`, and `curl`.

8 min read • View on GitHub • More from ankitvgupta

A compact container sits beside a pull request desk and turns a stream of lint marks into a visible review trail. The scene explains the article's core idea: the action does not just run a linter, it relocates the result into the conversation.
The key move is not finding style errors. It is surfacing them where reviewers already work.
Key Takeaways

The linter that talks back

Most lint checks disappear into logs. pycodestyle-action does the useful thing instead: it runs the linter and then posts the result back into the pull request thread. The warning does not sit off to the side. It lands beside the code review.

That is a small change with a big UX effect. The action turns style enforcement from a background gate into a visible reviewer, which makes the feedback harder to ignore and easier to act on.

How a shell script becomes a reviewer

Under the hood, the repo is almost aggressively plain. A Docker image gives the action a predictable shell, Python, curl, and jq. entrypoint.sh runs pycodestyle, captures the exit status, and keeps going long enough to package the output for GitHub's API.

set +e
OUTPUT=$(pycodestyle . 2>&1)
SUCCESS=$?

COMMENTS_URL=$(jq -r '.pull_request.comments_url' "$GITHUB_EVENT_PATH")
BODY=$(jq -n --arg body "$OUTPUT" '{body: $body}')
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" -H "Content-Type: application/json" --data "$BODY" "$COMMENTS_URL"

exit $SUCCESS

The details matter. jq protects the payload when the linter output contains punctuation, quotes, or newlines. The script also preserves the linter's original exit code, so the job can still fail after the comment is posted. If PRECOMMAND_MESSAGE is set, the action can prepend a short instruction before the raw lint output, which keeps the bot from sounding like a wall of text.

A close-up pipeline shows lint output moving from a terminal-like plate into a glass JSON capsule, through a narrow tube, and into a comment slip. It explains how the script uses `jq` and `curl` to turn raw output into a safe GitHub comment.
The whole mechanism is just careful plumbing. The hard part is not running the linter, it is moving the output safely into the right place.

The action is not magic. It is a carefully ordered path from linter output to PR comment, with JSON escaping and exit-code handling doing most of the work.

Why the stack is so small

The Dockerfile tells the same story. Start from python:3.7-alpine, add the two command-line tools the script needs, install pycodestyle, and stop there. There is no framework glue, no generated wrapper, and no extra abstraction to maintain.

FROM python:3.7-alpine
RUN apk add --no-cache curl jq
RUN pip install pycodestyle
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

That restraint is a design choice, not an omission. Minimal surfaces are easier to inspect, and inspection matters for a bot that writes into someone else's pull request. The smaller the stack, the easier it is to trust what the action is doing.

Compared with plain CI

Here is the tradeoff in one sentence: logs are storage, comments are interface. Plain CI tells you there is a style problem. pycodestyle-action tells you what the problem is in the same place reviewers are already discussing the change.

The left side shows a dense log scroll where style warnings are buried in a wall of text. The right side shows the same warnings as a clean note beside a pull request document, explaining why comment-based feedback is easier to act on.
The difference is not only visibility. It is context. A comment can join the review, while a log stays behind the curtain.
Default CI lintingpycodestyle-action
Feedback appears in CI logs.Feedback appears in the pull request thread.
Reviewers must hunt for it.Reviewers see it beside the diff.
The author translates logs into guidance.The bot does the translation.
Failing and explaining are separate steps.The action posts the explanation and then exits with the linter status.
Often adds tooling around the linter.Keeps the stack to shell, `jq`, `curl`, and `pycodestyle`.

That difference sounds subtle until you use it. A warning buried in logs is an interruption. A warning in the thread is part of the conversation.

A niche tool with a clean shape

This repo reads like a finished utility. It knows its audience, keeps its promise, and avoids turning a simple review problem into a platform project. That focus is why it stands out.

The lesson is bigger than Python style. When feedback has to change behavior, delivery matters as much as detection. pycodestyle-action gets that part right.