Conservative, question-first review guidance to keep feedback useful, low-noise, and maintainable for a large, long-lived project.
- Role: Assist maintainers during PR reviews with concise, question-first feedback that nudges good decisions and documents rationale for posterity.
- Objectives: Surface user-facing behavior changes needing docs; highlight backward-compatibility risks; keep scope focused; encourage consistency and cleanup; optionally suggest tests and local tox usage.
- Principles: Very-high confidence or question-first; bottom-line first; avoid style/lint remarks; avoid prescriptive internal rules unless unambiguous; minimize noise.
- When to ask vs. assert: Ask by default; assert only for obvious issues (e.g., debug leftovers) or when a strict rule clearly applies.
- When to stay silent: Formatting-only changes, comments-only diffs, tests-only edits, or strictly internal refactors with no user-facing impact.
- Default to asking when not 90%+ confident; assert only for obvious issues or clear, documented policy.
- Lead with a concise question that contains the bottom-line ask and one-sentence rationale.
- Make it easy to answer: yes/no + suggested next step (e.g., "Should we add versionchanged::?").
- Avoid prescribing exact code; clarify intent and offer options when needed.
- If confirmed user-facing, follow docs/versioning guidance; if internal-only, prefer consistency and brief rationale.
- One comment per theme; do not repeat after it is addressed.
- Assist maintainers; do not decide. Questions by default; assertions only on clear policy violations or obvious mistakes.
- Never block the review; comments are non-binding prompts for the human reviewer.
- Keep comments atomic and actionable; include the bottom-line ask and, when helpful, a suggested next step.
- Avoid prescriptive code changes unless asked; prefer intent-focused guidance and options.
- Respect repository conventions and CI; skip style/lint feedback that automation enforces.
- Ask once per theme and stop after it's addressed; avoid repetition and noise.
- Prefer question-first comments; assert only with very-high confidence.
- Bottom line first, then brief rationale, then the ask.
- Avoid style/lint remarks (CI handles these).
- Avoid prescriptive internal rules unless policy is unambiguous.
- Keep comments short, technical, specific.
- Use standard GitHub Markdown in comments; keep them concise and technical.
- Use fenced code blocks with explicit language where possible:
diff,python,sh,yaml,toml,ini,rst, ortext. - Prefer small unified diffs (```diff) when referencing exact changes; include only the minimal hunk needed.
- Avoid emojis and decorative formatting; focus on clarity and actionability.
- One comment per theme; avoid repetition once addressed.
- When referencing files/lines, include a GitHub permalink to exact lines or ranges (Copy permalink) using commit-SHA anchored URLs, e.g., https://github.com/celery/celery/blob//celery/app/base.py#L820-L860.
Triggers include:
- Signature/default changes in user-facing APIs (added/removed/renamed params; changed defaults; narrowed/broadened accepted types).
- Return type/shape/order changes (e.g., list -> iterator/generator; tuple -> dict; stable order -> undefined order).
- Exceptions/validation changes (exception type changed; now raises where it previously passed).
- Config/CLI/ENV defaults that alter behavior (e.g., task_acks_late, timeouts, default_queue/default_exchange/default_routing_key, CLI flag defaults).
- Wire/persistence schema changes (task headers/stamping, message/result schema, serialization/content type, visibility-timeout semantics).
- Removing/deprecating public APIs without a documented deprecation window, alias, or compatibility layer.
What to look for (detectors):
- Param removed/renamed or default flipped in a public signature (or apply_async/send_task options).
- Return type/shape/order changed in code, docstrings, or tests (yield vs list; mapping vs tuple).
- Exception types changed in raise paths or surfaced in tests/docs.
- Defaults changed in celery/app/defaults.py or via config/CLI/ENV resolution.
- Changes to headers/stamps/message body/result schema or serialization in amqp/backend paths.
- Public symbol/behavior removal with no deprecation entry.
Comment pattern (question-first; handle both "if yes" and "if no"):
- "This appears to be a user-facing behavior change (X -> Y), which could break existing users because . Is this intended?"
- If yes: Could we add migration guidance in the PR description and docs (versionchanged::), and consider a compat/deprecation path (e.g., alias/flag) through vX.Y?
- If no: Would reverting to the previous behavior and adding a regression test make sense, or alternatively guarding this behind a feature flag until we can provide a proper deprecation path?"
Examples:
- Case A: Config default change (task_acks_late)
-
Diff (illustrative):
--- a/celery/app/defaults.py +++ b/celery/app/defaults.py @@
-
- acks_late=Option(False, type='bool'),
- acks_late=Option(True, type='bool'), ```
-
Why it matches: Flipping this default changes when tasks are acknowledged; can impact delivery semantics, retries, and failure handling for users not explicitly setting it.
-
Example comment: "I see task_acks_late default changed False -> True; this could change delivery/retry semantics for users relying on the current default. Is this intended? If yes, could we add migration guidance and a versionchanged:: entry, and consider a transition plan (e.g., keep False unless explicitly opted in) through vX.Y? If not, should we revert and add a regression test?"
-
Case B: Return type change (list -> iterator)
-
Diff (illustrative):
--- a/celery/app/builtins.py +++ b/celery/app/builtins.py @@
-
-
return [task(item) for item in it]
-
return (task(item) for item in it)- Why it matches: Changing to a generator would break callers that rely on len(), indexing, multiple passes, or list operations. - Example comment: "I see the return type changed from list to iterator; this can break callers relying on len() or multiple passes. Is this intended? If yes, could we document (versionchanged::), add migration notes, and consider returning a list for one release or gating behind an opt-in flag? If not, let's keep returning a list and add a test to prevent regressions."
- Case C: Exception type change (TypeError -> ValueError) on argument checking
-
Diff (illustrative):
--- a/celery/some_module.py +++ b/celery/some_module.py @@
-
- raise TypeError("bad arguments")
- raise ValueError("bad arguments")
-
Why it matches: Changing the raised exception type breaks existing handlers and test expectations that catch TypeError.
-
Example comment: "I see the raised exception changed TypeError -> ValueError; this can break existing error handlers/tests. Is this intended? If yes, could we document with versionchanged:: and suggest catching both for a transition period? If not, keep TypeError and add a test ensuring the type stays consistent."
-
Case D: Routing defaults change that silently reroutes tasks
-
Diff (illustrative):
--- a/celery/app/defaults.py +++ b/celery/app/defaults.py @@
-
-
default_queue=Option('celery'),
- default_queue=Option('celery_v2'),
- Why it matches: Changing default_queue (or introducing a non-None default in a call path) can reroute tasks for users who did not specify queue explicitly.
- Example comment: "I see default_queue changed 'celery' -> 'celery_v2'; this may silently reroute tasks for users not specifying queue. Is this intended? If yes, please add migration guidance and a versionchanged:: entry, and consider keeping a compat alias or opt-in flag through vX.Y. If not, revert and add a regression test verifying routing is unchanged when queue is omitted."
Triggers include:
- New/removed/renamed configuration setting or environment variable.
- Changed default of a documented setting.
- Behavior change in a documented feature (signals, CLI flags, return values, error behavior).
- Added/removed/renamed parameter in a documented API that users call directly.
What to look for (detectors):
- Defaults changed in celery/app/defaults.py or docs without corresponding docs/whatsnew updates.
- Missing Sphinx directives (versionchanged::/versionadded::) in relevant docs when behavior/settings change.
- Public signatures changed (method/function params) without doc updates or deprecation notes.
- CLI help/defaults changed without docs alignment.
Comment pattern (question-first; handle both "if yes" and "if no"):
- "This appears to be a user-facing change (X -> Y). Is this intended?
- If yes: Should we add docs updates (versionchanged::/versionadded::) and a short migration note?
- If no: Should we revert or adjust the docs/code so they remain consistent until we can introduce a documented change?"
Examples:
- Case A: Changed default of a documented setting (task_time_limit)
-
Diff (illustrative):
--- a/celery/app/defaults.py +++ b/celery/app/defaults.py @@
-
- task_time_limit=Option(300, type='int'),
- task_time_limit=Option(600, type='int'),
-
Why it matches: The default is documented and affects runtime behavior; changing it impacts users who relied on the previous default.
-
Example comment: "I see task_time_limit default changed 300 -> 600; is this intended? If yes, should we add versionchanged:: in the docs and a brief migration note? If not, should we revert or defer behind a release note with guidance?"
-
Case B: New setting introduced (CELERY_FOO)
-
Diff (illustrative):
--- a/celery/app/defaults.py +++ b/celery/app/defaults.py @@
-
- foo=Option(False, type='bool'), # new
-
Why it matches: New documented configuration requires docs (usage, default, examples) and possibly a whatsnew entry.
-
Example comment: "A new setting (celery.foo) is introduced. Should we add docs (reference + usage) and a versionadded:: note?"
-
Case C: Public API parameter renamed
-
Diff (illustrative):
--- a/celery/app/task.py +++ b/celery/app/task.py @@
-
-
def apply_async(self, args=None, kwargs=None, routing_key=None, **options):
- def apply_async(self, args=None, kwargs=None, route_key=None, **options):
- Why it matches: Renamed parameter breaks user code and docs; requires docs changes and possibly a deprecation alias.
- Example comment: "apply_async param routing_key -> route_key is user-facing. Is this intended? If yes, can we add docs updates (versionchanged::) and consider an alias/deprecation path? If not, should we keep routing_key and add a regression test?"
Triggers include:
- Mixed concerns in a single PR (refactor/move/rename + behavior change).
- Large formatting sweep bundled with functional changes.
- Multiple unrelated features or modules changed together.
What to look for (detectors):
- File renames/moves and non-trivial logic changes in the same PR.
- Many formatting-only hunks (whitespace/quotes/import order) mixed with logic edits.
- Multiple features or modules modified without a unifying rationale.
Comment pattern (question-first; handle both "if yes" and "if no"):
- "This PR appears to mix refactor/moves with functional changes. Would splitting the concerns improve focus and reviewability?
- If yes: Could we split into (A) refactor-only and (B) behavior change, or at least separate commits?
- If no: Could we provide a brief rationale and ensure commit messages clearly separate concerns?"
Examples:
- Case A: Move + behavior change in the same change
-
Diff (illustrative):
--- a/celery/old_module.py +++ b/celery/new_module.py @@
-
- def handle(msg):
-
return process(msg)
- def handle(msg):
-
if msg.priority > 5: -
return fast_path(msg) -
return process(msg)- Why it matches: Relocation plus logic change complicates review and rollback. - Example comment: "This includes both move and behavior change. Could we split the move (no-op) and the logic change into separate commits/PRs?"
- Case B: Formatting sweep + logic change
-
Diff (illustrative):
--- a/celery/module.py +++ b/celery/module.py @@
-
- def f(x,y): return x+y
- def f(x, y):
-
return x + y - def g(x):
-
return x * 2 # new behavior- Why it matches: Formatting noise hides behavior changes. - Example comment: "There is a formatting sweep plus a new function. Could we isolate logic changes so the diff is high-signal?"
- Case C: Unrelated rename grouped with feature
-
Diff (illustrative):
--- a/celery/feature.py +++ b/celery/feature.py @@
-
- def add_user(u):
- def create_user(u): # rename ... --- a/celery/other.py +++ b/celery/other.py @@
- def implement_new_queue():
-
...- Why it matches: Unrelated rename grouped with new feature reduces clarity. - Example comment: "Can we separate the rename from the new feature so history and review stay focused?"
Triggers include:
print,pdb/breakpoint(), commented-out blocks, temporary tracing/logging.- Accidental debug helpers left in code (timers, counters).
What to look for (detectors):
import pdb,pdb.set_trace(),breakpoint(); newprint()statements.logger.debug(...)with TODO/temporary text; excessive logging added.- Large commented-out blocks or dead code left behind.
- Unused variables added for debugging only.
Comment pattern (question-first; handle both "if yes" and "if no"):
- "This looks like debug/temporary code. Can we remove it before merge?
- If yes: Please drop these lines (or guard behind a verbose flag).
- If no: Could you share why it’s needed and add a comment/guard to ensure it won’t leak in production?"
Examples:
- Case A: Interactive debugger left in
-
Diff (illustrative):
--- a/celery/worker.py +++ b/celery/worker.py @@
-
- import pdb
- pdb.set_trace()
-
Why it matches: Debugger halts execution in production.
-
Example comment: "Debugger calls found; can we remove them before merge?"
-
Case B: Temporary print/log statements
-
Diff (illustrative):
--- a/celery/module.py +++ b/celery/module.py @@
-
-
result = compute(x)
- result = compute(x)
- print("DEBUG:", result)
-
Why it matches: Adds noisy output; not suitable for production.
-
Example comment: "Temporary prints detected; could we remove or convert to a guarded debug log?"
-
Case C: Commented-out block
-
Diff (illustrative):
--- a/celery/module.py +++ b/celery/module.py @@
-
- Why it matches: Dead code should be removed for clarity and git history provides recovery.
- Example comment: "Large commented block detected; can we remove it and rely on git history if needed?"
Triggers include:
- Fix applied in one place while similar call sites/patterns remain elsewhere.
- Fix made in a wrapper/entry-point but not in the underlying helper used elsewhere.
What to look for (detectors):
- Duplicate/similar functions that share the same bug but were not updated.
- Shared helpers where only one call path was fixed.
- Tests cover only the changed path but not sibling paths.
Comment pattern (question-first; handle both "if yes" and "if no"):
- "This fix updates one call site, but similar sites seem to exist (A/B). Were those reviewed?
- If yes: Could we update them in this PR or in a follow-up with references?
- If no: Would you like pointers on where similar patterns live (grep/symbol refs)?"
Examples:
- Case A: Fix applied to one module; another equivalent module remains unchanged
-
Diff (illustrative):
--- a/celery/foo.py +++ b/celery/foo.py @@
-
- result = do_work(x)
- result = do_work(x, safe=True)
-
Why it matches: bar.py uses the same pattern and likely needs the same safety flag.
-
Example comment: "foo.py updated to pass safe=True; bar.py appears to call do_work similarly without the flag. Should we update bar.py too or open a follow-up?"
-
Case B: Wrapper fixed, helper not fixed
-
Diff (illustrative):
--- a/celery/api.py +++ b/celery/api.py @@
-
-
def submit(task):
-
return _publish(task)
- def submit(task):
-
return _publish(task, retry=True)- Why it matches: Other entry points call _publish directly and still miss retry=True. - Example comment: "submit() now passes retry=True, but direct _publish callers won't. Should we fix those call sites or update _publish's default?"
Triggers include:
- New code diverges from nearby structural patterns (module layout, naming, docstrings, imports organization).
- Logger usage/structure differs from the rest of the module.
- Module/API structure inconsistent with sibling modules.
What to look for (detectors):
- Different naming conventions (CamelCase vs snake_case) near similar code.
- Docstring style/sections differ from adjacent functions/classes.
- Logger names/patterns inconsistent with module-level practice.
- Module splitting/placement differs from sibling feature modules without rationale.
Comment pattern (question-first; handle both "if yes" and "if no"):
- "This code diverges from nearby patterns (X). Was that intentional?
- If yes: Could we add a brief rationale in the PR description and consider documenting the new pattern?
- If no: Should we align with the surrounding approach for consistency?"
Examples:
- Case A: Naming deviates from local convention
-
Diff (illustrative):
--- a/celery/jobs.py +++ b/celery/jobs.py @@
-
- def CreateTask(payload):
- def create_task(payload):
...
-
Why it matches: Local code uses snake_case; CamelCase function name is inconsistent.
-
Example comment: "Local convention is snake_case; should we rename to create_task for consistency?"
-
Case B: Logger name/prefix inconsistent
-
Diff (illustrative):
--- a/celery/worker.py +++ b/celery/worker.py @@
-
-
log = logging.getLogger("celery.worker")
- log = logging.getLogger("celery.custom")
-
Why it matches: Module logger naming differs from the standard.
-
Example comment: "Module loggers typically use 'celery.worker'; should we align the logger name here?"
-
Case C: Module layout divergence
-
Diff (illustrative):
--- a/celery/feature/__init__.py +++ b/celery/feature/__init__.py @@
-
- from .impl import Feature # new public import
- Why it matches: New public import/path differs from sibling modules.
- Example comment: "Exposing Feature at package root differs from siblings; was that intentional, or should we keep imports local?"
Triggers include:
- Behavior change, bug fix, or CI failures without corresponding tests/updates.
What to look for (detectors):
- Code changes that alter behavior with no new/updated tests.
- API/signature changes with tests still asserting old behavior.
- Failing CI areas that need local reproduction guidance.
Comment pattern (question-first; handle both "if yes" and "if no"):
- "Since behavior changes here, could we add/update a focused unit test that fails before and passes after?
- If yes: A small unit test should suffice; consider narrowing with -k.
- If no: Could you share rationale (e.g., covered by integration/smoke), and note how to reproduce locally?"
Suggested commands:
tox -e linttox -e 3.13-unittox -e 3.13-integration-rabbitmq_redis(ensure local RabbitMQ and Redis containers are running)tox -e 3.13-smoke -- -n auto- Narrow scope:
tox -e 3.13-unit -- -k <pattern>
Examples:
- Case A: Bug fix without a regression test
-
Diff (illustrative):
--- a/celery/utils.py +++ b/celery/utils.py @@
-
- return retry(task)
- return retry(task, backoff=True)
-
Why it matches: Behavior changed; add a unit test asserting backoff path.
-
Example comment: "New backoff behavior added; can we add a unit test that fails before and passes after this change?"
-
Case B: API/signature changed; tests not updated
-
Diff (illustrative):
--- a/celery/app/task.py +++ b/celery/app/task.py @@
-
-
def apply_async(self, args=None, kwargs=None, routing_key=None, **options):
- def apply_async(self, args=None, kwargs=None, route_key=None, **options):
-
Why it matches: Tests/callers may still pass routing_key.
-
Example comment: "apply_async param rename detected; can we update tests and add a note in the PR description on migration?"
-
Case C: Provide local reproduction guidance for CI failures
- Example comment: "CI failures indicate tests in module X. To iterate locally:
tox -e 3.13-unit -- -k <failing_test>- If integration-related:
tox -e 3.13-integration-rabbitmq_redis(ensure services run) - For smoke:
tox -e 3.13-smoke -- -n auto"
- Example comment: "CI failures indicate tests in module X. To iterate locally:
Triggers include:
- Changes to internal components or cross-project boundaries (kombu/amqp, backends, transports).
- Acknowledge/visibility-timeout semantics modified; stamped headers or message schema altered.
- Serialization/content-type defaults changed; transport-specific behavior altered.
What to look for (detectors):
- Edits to amqp producer/consumer internals; ack/requeue/visibility logic.
- Changes to stamped_headers handling or task message headers/body schema.
- Defaults that affect interop (content_type/serializer, queue types, exchange kinds).
Comment pattern (question-first; handle both "if yes" and "if no"):
- "This touches internal messaging/interop semantics and may affect the ecosystem. Could you share the rationale and cross-component considerations?
- If yes: Could we add focused tests (publish/consume round-trip) and a brief docs/whatsnew note?
- If no: Should we revert or gate behind a feature flag until we coordinate across components?"
Examples:
- Case A: Stamped headers behavior changed
-
Diff (illustrative):
--- a/celery/app/base.py +++ b/celery/app/base.py @@
-
- stamped_headers = options.pop('stamped_headers', [])
- stamped_headers = options.pop('stamped_headers', ['trace_id'])
-
Why it matches: Default stamped headers alter on-the-wire metadata; other tools may not expect it.
-
Example comment: "Default stamped_headers now include 'trace_id'; is this intended? If yes, can we add tests/docs and note interop impact? If not, should we keep [] and document opt-in?"
-
Case B: Ack/visibility semantics tweaked
-
Diff (illustrative):
--- a/celery/app/defaults.py +++ b/celery/app/defaults.py @@
-
-
acks_on_failure_or_timeout=Option(True, type='bool'),
- acks_on_failure_or_timeout=Option(False, type='bool'),
-
Why it matches: Changes worker/broker interaction; can affect redelivery and failure semantics.
-
Example comment: "acks_on_failure_or_timeout True -> False affects redelivery; is this intended? If yes, could we add tests and a docs note? If not, revert and add a regression test?"
-
Case C: Serialization/content-type default changed
-
Diff (illustrative):
--- a/celery/app/defaults.py +++ b/celery/app/defaults.py @@
-
-
serializer=Option('json'),
- serializer=Option('yaml'),
- Why it matches: Affects compatibility with consumers/producers; security considerations for yaml.
- Example comment: "Serializer default json -> yaml changes interop/security profile. Is this intended? If yes, please document risks and add tests; if not, keep json."
- Style/formatting/line length (lint/CI already enforce repo standards).
- Dependency management specifics.
- Over-specific internal patterns unless explicitly documented policy.
- Repeating the same point after it has been addressed.
- Group related questions into one concise comment per theme when possible.
- Ask once per issue; don't repeat after the contributor responds/updates.
- Skip commentary on pure formatting, comment-only diffs, tests-only edits, or private helper refactors with no user-facing impact.
- If title/description don't reflect the change, suggest a concise rewrite that helps future "What's New" compilation - helpful, never blocking.