Conversation
Signed-off-by: Zhao Zhuoluo <2715530896@qq.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Fix the grammatical article errors in the test docstrings at lines 1073 and 1662.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Fixes MQTT 5 QoS 2 failure reporting when a broker rejects a publish via PUBREC.
Changes:
- Propagates failed PUBREC reason codes to callbacks.
- Suppresses PUBREL and cleans up message state.
- Adds comprehensive regression tests.
| File | Summary |
|---|---|
tests/test_client.py |
Adds PUBREC failure-path coverage. Critical: change “A MQTT” to “An MQTT” at lines 1073 and 1662 (3 votes). |
src/paho/mqtt/client.py |
Implements failed PUBREC handling and cleanup. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Please on your fork, go into the settings and click "enable Workflows" so everyone can see the results of the test suite |
Signed-off-by: Zhao Zhuoluo <2715530896@qq.com>
bbeed61 to
4957176
Compare
|
Workflows are now enabled on my fork, and the pre-commit, lint, and Python 3.9–3.14 tox runs have all completed successfully. Thanks! |
| if mid in self._out_messages: | ||
| msg = self._out_messages[mid] | ||
|
|
||
| # MQTT 5: a PUBREC with a failure reason code (>= 0x80) ends |
There was a problem hiding this comment.
Not a big fan of this kind of explanation comments. Code should be self-explanatory (which in this case I think it is).
There was a problem hiding this comment.
Thanks, agreed. I’ve removed the explanatory comment.
| "on_connect", | ||
| ("on_publish", 1, 0x87), | ||
| ("on_publish", 2, 0), | ||
| ] |
There was a problem hiding this comment.
missing newlines, no linter?
There was a problem hiding this comment.
Good catch, thank you! I added the missing blank lines between the top-level classes and ran an explicit E302/E303 check over the changed Python files in addition to the project’s existing checks.

Problem and root cause
Fixes #895.
When a broker rejects a QoS 2 publish, MQTT 5 returns the failure reason code in PUBREC, for example
0x87 Not authorized.Currently,
Client._handle_pubrec()unpacks this reason code but does not use it. It unconditionally moves the outgoing message tomqtt_ms_wait_for_pubcompand sends PUBREL. Mosquitto then responds with a PUBCOMP without a reason code, causing_handle_pubackcomp("PUBCOMP")to complete the message through_do_on_publish()with an implicit Success.As a result, the application receives
on_publish(..., Success)even though the broker rejected and discarded the message.This was reproduced against Mosquitto 2.0.22 using MQTT 5 and an ACL that only allowed publishing to
allowed/#:on_publishreportsNot authorized (0x87)because the PUBACK reason code reaches the callback.0x87, Paho sends PUBREL, andon_publishultimately reportsSuccess (0x00).The behavior was reproduced in three independent runs on master commit
26cf3e3503bfde014f7c67dd12f4bed23f74a575, using a fresh broker container and client identifier for each run.MQTT 5 specification behavior
The MQTT 5 specification requires that:
0x80.0x80or greater completes the PUBLISH exchange as a failure and the PUBLISH must not be retransmitted.These requirements are defined in sections 3.5.2.1, 4.3.3, and 4.4, including requirements MQTT-4.3.3-4 and MQTT-4.4.0-2.
Fix
When
_handle_pubrec()receives an MQTT 5 PUBREC with a failure reason code (>= 0x80), it now:_do_on_publish(mid, reason_code, properties)path;_out_messages;wait_for_publish().The existing behavior is preserved for:
0x80;The fix reuses
_do_on_publish()rather than duplicating its callback and state-cleanup logic.MessageInfo.rcretains its existingMQTTErrorCodemeaning. The broker-provided failure reason remains available throughon_publish, consistent with the existing QoS 1 PUBACK failure path.Callback API compatibility
Callback API v2 continues to use:
For a failed PUBREC, it receives the original PUBREC reason code and properties. When the packet contains no properties, an empty
Properties(PacketTypes.PUBREC)object is provided.Callback API v1 retains its existing three-argument signature:
It cannot expose the reason code, but the callback still runs once and the message no longer remains pending.
Tests
The new
tests/test_client.py::TestPubrecErrorcoverage contains 9 test methods and 32 parametrized cases across TCP and Unix sockets. It uses the existing in-process fake broker and requires no external broker.Coverage includes:
0x80,0x83,0x87,0x90,0x91,0x97, and0x99;on_publish;_out_messagesand_inflight_messages;wait_for_publish();0x00PUBREC behavior;PacketTypes.PUBRECproperty type with both received and synthesized properties;max_inflight_messagesis 1;The new failure-path tests fail on unmodified master and pass with this change.
Full local test result:
The skipped tests depend on
paho.mqtt.testingand are unrelated to this change.Fault-injection checks covering the failure condition, PUBREL suppression, callback result, state cleanup, and PUBREC property type are each detected by the new tests.
Docker A/B verification
The behavior was also verified against Mosquitto 2.0.22 using MQTT 5, username/password authentication, and an ACL allowing writes only to
allowed/#.Three before/after rounds were run with a fresh broker container and client identifier each time.
Before the fix:
After the fix:
An allowed QoS 2 publish continues to complete through PUBREC → PUBREL → PUBCOMP. The other tested scenarios—allowed QoS 0/1/2 and denied QoS 0/1—behave identically before and after the change.
Compatibility