Case Study: The Pull Request Rejection
A lesson in open source, double standards, and poor leadership
Date: February 5, 2026 Author: Jacob Sussmilch
Executive Summary
A contributor submitted a documented, tested pull request to [organisation]/[repository] proposing a coordinated protocol upgrade. The maintainer closed it without reviewing the code, cited standards contradicted by his own commit history, and permanently banned the contributor after a single exchange. The contributor made a tactical error — submitting code before opening an issue. The maintainer’s response was disproportionate, dishonest about its reasons, and revealed a project that enforces hierarchy while claiming to enforce quality.
The Pull Request
Repository: [organisation]/[repository] PR #5: feat: Communication Protocol V2 - msgpack, inline results, BLPOP URL: [redacted]
What the PR proposed:
- Replace
time.sleep(0.1)polling with BLPOP blocking reads (~50ms latency reduction) - Inline results for small payloads (<1MB) to eliminate network round-trips
- Replace JSON+base64 encoding with msgpack (~25% size reduction)
- Wire protocol versioning via a version byte in the msgpack header, enabling future upgrades without breaking existing deployments
- 43 unit tests, integration tests, and load tests
Diff statistics: +1,387 / -47 lines (approximately 80% was test code)
The Exchange
Maintainer’s Initial Response ([maintainer])
I’m closing this PR. Some of the ideas here are good, but this is not the right way to approach improving production code like this. PRs need to be inspired by real-world issues, not an LLMs assessment of bad implementation.
- This is a huge green diff, that’s bad! [repository] is mini!
- Public tests is a good idea, but should start with a single full integration test in CI
- some of these improvements make sense, but PRs need a single improvement at the time
Contributor’s Response
@[maintainer] Your assessment doesn’t make complete sense to me.
This is a breaking change upgrade of the protocol - there are going to be changes that need to happen at once. Most of the green diff is 80% test code, which is what you’d want for an upgrade like this. msgpack is a negligible addition to the dependencies that doesn’t have any dependencies itself - it’s the right tool for the job. The production line changes are 214 lines, which I think is reasonable.
msgpack changing the wire format and inline results requiring the new message types in that wire format are interdependent changes. If I split msgpack and inline results, you’d have two breaking changes instead of one coordinated upgrade.
Regarding the BLOP changes (10 lines), I agree it should be a separate PR. And yes, I could’ve opened an issue first to discuss the upgrade. However, I think the PR itself is still valid.
With regards to your comment about being inspired by ‘real-world issues’ to make changes, it doesn’t really make sense to me. Polling with time.sleep(0.1) instead of blocking reads, unnecessary network round-trips for small payloads, and base64-encoding binary inside JSON are textbook inefficiencies. One of the first things I look at on projects like this is the communication protocol.
Sure, I had an LLM write many of the changes. However, I think enough people would understand why I submitted the PR for these changes. If the PR doesn’t fit your process, let me know how to follow it rather than just ditching the changes at first glance.
You closed this without offering a path forward, and I think that is unfair. I understand you might be busy, and [organisation] might not want to accept code for the production systems from an unknown contributor; however, this is the point of PR reviews.
Would you be open to a smaller PR that addresses only the BLPOP polling?
Cheers, [contributor]
Maintainer’s Final Response (and permanent ban)
I permanently blocked you from [organisation]. As I said, the ideas are good, I don’t like JSON either. Using central redis for small results is a good idea.
However, ALL PRs to ALL software projects need to be inspired real-world issues. If you make “technical” improvements that don’t address a current use case of a real user, you’re wasting everyone’s time. You’re adding complexity and most likely introducing bugs someone else has to deal with no known improvement. Does the json overhead matter in the use-cases [repo] is used? Is it worth the added lines and making the readable parts of the string unreadable in ASCII? You have no idea, because you’re not a real user nor have you made any attempt to be one.
The only exception to this rule is when you have PRs that simplify the code with clean red diffs. Obviously not the case here.
What the Contributor Got Wrong
The contributor should have opened an issue first. A breaking protocol change is a conversation before it is a contribution, and arriving with a finished implementation as a first interaction skips that conversation. However that is not to say opening a pull request without prior discussion is unsolicited, as the PR review itself is solicitation process.
The maintainer would have been within his rights to say: “Good ideas, wrong sequence — open an issue, let’s discuss the upgrade, then we’ll figure out the right way to land it.”
Instead, the contributor received a rejection without review, standards cited but not followed, and a permanent ban in response to a polite question about how to contribute. The sequencing mistake was the contributor’s. Everything that followed was the maintainer’s.
Analysis: What Actually Happened
The Stated Objections vs. Reality
| Objection | Counter | Problem |
|---|---|---|
| ”LLM-generated code” | Origin doesn’t determine quality | Genetic fallacy |
| ”Huge green diff” | 80% was test code; production delta was 214 lines | Didn’t examine the actual content |
| ”Single improvement per PR” | msgpack + inline results share wire format; splitting = two breaking changes | Rule isn’t documented anywhere |
| ”Not a real user” | No docs on use cases; no onboarding path | Catch-22: can’t contribute without being a user, can’t become a user without engaging |
| ”Must be inspired by real-world issues” | sleep(0.1) polling and base64 in JSON are textbook inefficiencies | Philosophy invented post-hoc to justify rejection |
What the Repository Lacked
The maintainer enforced rules that exist nowhere in writing:
- No CONTRIBUTING.md
- No issue templates
- No documented contribution process
- No architecture docs or use case documentation
- README only says: “Minimal library for distributed python work”
The Double Standard: Maintainer’s Own Commit History
The maintainer’s commits to the same repository (from the same day):
Revert "task object" - [maintainer] committed 1 hour ago
task object - [maintainer] committed 1 hour ago
must be resourcelimit error! - [maintainer] committed 3 hours ago
No leeway, crack the whip! - [maintainer] committed 3 hours ago
why custom alerts - [maintainer] committed 3 hours ago
simpler - [maintainer] committed 3 hours ago
much simpler - [maintainer] committed 3 hours ago
less redis action - [maintainer] committed 3 hours ago
fix tests - [maintainer] committed 4 hours ago
Observations:
- Pushing directly to master (no pull request review)
- Reverting own commits within an hour
- Commit messages that are questions: “why custom alerts”
- Commit messages that explain nothing: “simpler”, “much simpler”
- Motivational poster messages: “No leeway, crack the whip!”
The contrast:
| The PR | Maintainer’s Own Commits |
|---|---|
| Detailed description with benchmarks | ”No description provided” on many PRs |
| Commit messages explain why | Commit messages are UUIDs or “fix” |
| 43 tests documented | No test documentation visible |
| Structured change breakdown | Push to master, revert an hour later |
The maintainer isn’t enforcing standards. He’s enforcing hierarchy. Insiders cowboy-commit to master with no review; outsiders get rejected despite having documentation and demonstration of rigour.
What Was Really Happening
The “LLM-generated” comment wasn’t about code quality. It was a gut reaction to something that looked too polished from someone without social standing in the project.
The real objection was: “Who are you to submit this?”
That’s a valid gatekeeping stance for a private project. However, it’s an open-source repository and dressing up such a dismissal as process concerns is dishonest when the maintainer’s own commit history is “much simpler” followed by a revert an hour later.
A permanent ban after one interaction — where the contributor politely asked “how should I contribute?” — is not leadership. It’s conflict avoidance dressed as authority. The maintainer didn’t want to have the conversation where he’d have to reconcile his stated principles with his own behaviour.
The maintainer said twice that the ideas were good. He agreed that JSON was a problem. He acknowledged that inlining small results in Redis made sense. If he genuinely believed this, the obvious next step was to say: “Here’s how to break this into changes we can accept.” That is what leadership looks like. He did none of that. He agreed with the substance, rejected the contribution, and banned the contributor.
Key Observations
On the response, not the contribution: Whether the PR was well-sequenced is a separate question from whether the response was proportionate. Even a poorly timed contribution does not warrant rejection without review, fabricated procedural objections, and a permanent ban after one exchange.
On unwritten rules: You can’t fault someone for violating rules that haven’t been published. If you want specific contribution norms, write them down. And demonstrate them yourself.
On the “real user” requirement: How would anyone become a real user of a 24-star library with no documentation and a maintainer who blocks people who try to engage? This is a closed loop.
On in-group dynamics: Some projects are clubs, not communities. That’s the maintainer’s right, but calling it “standards” when it’s actually gatekeeping is dishonest.
On “probably has bugs”: The maintainer said: “You’re adding complexity and most likely introducing bugs someone else has to deal with.” But he didn’t review the code. This is circular:
- “Your code probably has bugs”
- Refuses to review the code
- Uses the assumed bugs as justification for not reviewing
The entire point of a PR review is to find bugs before they hit production. He didn’t find any bugs — he didn’t look. He assumed they exist because the PR came from an outsider.
A real review would look like: “Line 47 doesn’t handle the timeout edge case” or “Missing test for malformed msgpack payloads.” That’s engagement. That’s what maintainers do. Instead, he said “there are probably bugs” and closed it.
The PR included 43 tests. If the maintainer thought there were bugs, he could have pointed to gaps in test coverage. He didn’t, because he never looked. He treated “unknown contributor” as a proxy for “low quality code” — which isn’t unreasonable as a prior, but you’re supposed to update that prior by actually reading the code. He skipped that step and went straight to rejection.
On timing and “existing users”: The maintainer claimed the contributor wasn’t considering existing users. But the repo was only recently made public. It has 24 stars and 2 forks.
This timing argument actually works in favour of the PR:
- Few external users = low cost of breaking changes
- Early in public lifecycle = ideal time to fix protocol debt
- Less code complexity now = easier to refactor cleanly
- The project is versioned at v0.1 — breaking changes are expected at this stage
If you’re going to swap out JSON+base64 for msgpack, you want to do it before people build on top of it, not after. The “think of the users” framing implies a mature ecosystem with dependents. A 24-star repo that just went public, at v0.1, has almost none.
The PR also introduced wire protocol versioning — a version byte in the msgpack header that would allow future protocol upgrades without breaking existing deployments. This is standard engineering practice: you version the wire format so that clients and servers can negotiate compatibility. The upgrade could have been released on a separate branch, and the supposed “user base” would simply reference the version they need. This is how software works. The maintainer’s concern about breaking users was not only premature for a v0.1 project — the PR itself addressed it with a versioning mechanism he never reviewed.
The contradiction is clear: the maintainer said the contributor wasn’t considering existing users, but also said the contributor wasn’t a “real user” because the project is essentially internal tooling. Both can’t be true. Either there’s a meaningful external user base whose needs should be considered, or it’s internal tooling and outsiders can’t understand the use case. He picked whichever framing let him reject in the moment.
The “existing users” he’s protecting are himself and his team. The objection isn’t “you’ll break things for the community” — it’s “I don’t want to review this.”
Lessons
The Gap Between Open and Ready
The repository was released without the infrastructure for meaningful external engagement. No contribution guidelines. No documented use cases or architecture. No issue templates. No explanation of what the project does for its users or what problems it considers worth solving. The README said “minimal library for distributed python work” and little else.
The contributor had no reference points to calibrate against — no way to understand the project’s priorities, its deployment context, or what the maintainer considered in-scope. The maintainer had no framework to evaluate a stranger’s PR against — no documented standards to point to, no process to fall back on, no shared language for saying “good direction, wrong approach.” When a contribution arrived that did not fit his unstated expectations, he had nothing to reach for except instinct and authority.
That absence of infrastructure explains how the conversation went wrong. It does not explain what happened next. The contributor asked how to contribute. The maintainer made the gap permanent. That is not a failure of preparation. That is a choice.
On Not Becoming Like This
The contributor made tactical mistakes. He should have opened an issue before writing code. He should have introduced himself and asked about the project’s direction before proposing a breaking change. Those are lessons to carry forward.
But none of that justifies what happened next. Closing without reviewing the code. Citing standards contradicted by his own commit history. Permanently banning someone whose only follow-up was “how should I contribute instead?” Those are not responses to a tactical error. They are the reflexes of someone who has confused authority with competence, and comfort with correctness.
What this case study reveals is someone who has been in a position of unchallenged authority long enough that he has forgotten how to be questioned. That is not inevitable. It is a choice made repeatedly over time — every time he shut down instead of engaged, every time he enforced rules he did not follow, every time he protected his ego instead of his standards.
Practical Commitments
- Open issues before opening pull requests for breaking changes.
- Write down the contribution standards you believe in, and follow them yourself.
- When someone gets it wrong, show them how to get it right.
- If you close a contribution, offer a path forward.
- If you are questioned, sit with it.
- Stay in communities where you are not the authority. Get reviewed. Get rejected. Stay calibrated.
Final Thought
The disappointment comes from expecting better. That is not naivety — it is having standards. The commitments above exist because of what this interaction cost. Worth making sure it pays for something.