Skip to content

MPT-23854 Improve error handling - #96

Open
albertsola wants to merge 1 commit into
mainfrom
refactor/improve-error-handling
Open

MPT-23854 Improve error handling#96
albertsola wants to merge 1 commit into
mainfrom
refactor/improve-error-handling

Conversation

@albertsola

@albertsola albertsola commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

🤖 AI-generated PR — Please review carefully.

What was done

Improved and documented the backend's error handling:

  • Exception hierarchy — all package exceptions are now rooted in a single ExtensionError base. Added ConfigurationError for missing or invalid configuration and re-rooted UpstreamAPIError under the base.
  • Domain exceptions instead of RuntimeError — replaced the six bare RuntimeError raises: MPT client env vars (mpt_client.py), MPT_DATABASE_URL resolution and the not-open guard (persistence/postgres/database.py), unsupported DSN parameters (persistence/postgres/connection.py), non-TLS sslmode with Entra ID auth (persistence/postgres/auth.py), and the impossible-row guard (persistence/postgres/insights.py).
  • Narrow catchingExecutionTracker and StatementProcessingRecorder now catch Exception instead of BaseException; typer.Exit is still caught (it subclasses RuntimeError). KeyboardInterrupt/asyncio.CancelledError propagate without finalising the insight row, which is documented and covered by tests.
  • Single-site logging — removed the duplicate logger.warning calls at the four MPT API error-translation sites (services/charges.py, services/statements.py, services/bucket_delete.py, cli/commands/push_estimates_by_id.py); the boundary that handles the failure owns the single report.
  • Documentation — added docs/error-handling.md describing the exception hierarchy, the runtime error flow across the recording/notification layers, when Teams notifications trigger, and the timeout/retry posture; linked it from README.md, AGENTS.md, and docs/architecture.md.

No business behavior changes.

Testing

  • make test on all affected test files: 136 passed (includes new typer.Exit, KeyboardInterrupt, and asyncio.CancelledError coverage for both tracker context managers).
  • make check (ruff, flake8, mypy): clean.
  • Pre-commit hooks passed on commit.

Closes MPT-23854

  • Added ExtensionError and ConfigurationError to define the package exception hierarchy.
  • Replaced generic RuntimeError raises with domain-specific exceptions.
  • Narrowed exception handling from BaseException to Exception while preserving typer.Exit handling.
  • Removed duplicate warning logs during MPT API error translation.
  • Added documentation for error handling, notifications, timeout and retry behavior, and reruns.
  • Added tests for exception handling, cancellation, keyboard interrupts, and configuration errors.

@albertsola
albertsola requested a review from a team as a code owner July 31, 2026 13:40
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

✅ Found Jira issue key in the title: MPT-23854

Generated by 🚫 dangerJS against 40053f4

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Error handling

Layer / File(s) Summary
Exception contracts and configuration validation
backend/mpt_usage_reporting_extension/exceptions.py, backend/mpt_usage_reporting_extension/mpt_client.py, backend/mpt_usage_reporting_extension/persistence/postgres/*, backend/tests/persistence/postgres/*, backend/tests/test_mpt_client.py
Added ExtensionError and ConfigurationError. Updated configuration and database state failures to use these exceptions.
Runtime error propagation and logging
backend/mpt_usage_reporting_extension/cli/commands/push_estimates_by_id.py, backend/mpt_usage_reporting_extension/services/*, backend/tests/cli/commands/*, backend/tests/services/*
Stopped catching BaseException in execution tracking. Removed warning logs from upstream error paths while preserving exception translation.
Error-handling documentation and guidance
AGENTS.md, README.md, docs/architecture.md, docs/error-handling.md
Added the error-handling guide and linked it from repository guidance and documentation indexes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Documentation Up To Date ✅ Passed Added docs/error-handling.md covering the new exception hierarchy, configuration boundaries, API translation, tracking, cancellation, notifications, and retry behavior; linked from README.md and ar...

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/mpt_usage_reporting_extension/services/execution_tracker.py`:
- Line 45: Update both tracker context-manager docstrings and
docs/error-handling.md to document that only escaping Exception subclasses
finalize rows as failed. Add coverage for asyncio.CancelledError and
KeyboardInterrupt in both context-manager test suites, while preserving existing
ordinary-exception and typer.Exit(code=1) behavior.

In `@docs/error-handling.md`:
- Line 75: Update the “Every tracked execution produces exactly one card”
statement in the execution-notification documentation to make the guarantee
conditional on notifications being enabled, while preserving the existing
behavior that disabled notifications send no card.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 4b83b969-cb30-4c42-ab35-d31aeff46eff

📥 Commits

Reviewing files that changed from the base of the PR and between ff64490 and 9046c46.

📒 Files selected for processing (23)
  • AGENTS.md
  • README.md
  • backend/mpt_usage_reporting_extension/cli/commands/push_estimates_by_id.py
  • backend/mpt_usage_reporting_extension/exceptions.py
  • backend/mpt_usage_reporting_extension/mpt_client.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/auth.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/connection.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/database.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/insights.py
  • backend/mpt_usage_reporting_extension/services/bucket_delete.py
  • backend/mpt_usage_reporting_extension/services/charges.py
  • backend/mpt_usage_reporting_extension/services/execution_tracker.py
  • backend/mpt_usage_reporting_extension/services/statements.py
  • backend/tests/cli/commands/test_push_estimates_by_id.py
  • backend/tests/persistence/postgres/test_auth.py
  • backend/tests/persistence/postgres/test_connection.py
  • backend/tests/persistence/postgres/test_database.py
  • backend/tests/services/test_bucket_delete.py
  • backend/tests/services/test_charges.py
  • backend/tests/services/test_statements.py
  • backend/tests/test_mpt_client.py
  • docs/architecture.md
  • docs/error-handling.md
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • softwareone-platform/mpt-extension-skills (manual)
💤 Files with no reviewable changes (4)
  • backend/mpt_usage_reporting_extension/services/statements.py
  • backend/mpt_usage_reporting_extension/services/charges.py
  • backend/mpt_usage_reporting_extension/cli/commands/push_estimates_by_id.py
  • backend/mpt_usage_reporting_extension/services/bucket_delete.py
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: mpt-extension-usage-reporting (Build Build Image)
  • GitHub Check: mpt-extension-usage-reporting (Prerequisites Create standard build artifact)
  • GitHub Check: mpt-extension-usage-reporting (Prerequisites Set the version)
  • GitHub Check: build
🧰 Additional context used
📓 Path-based instructions (10)
README.md

📄 CodeRabbit inference engine (AGENTS.md)

README.md: When applicable, read README.md first for repository purpose, quick start, and the documentation map.
Keep README.md short and navigational; put topic-specific behavior under docs/.

Files:

  • README.md
**/*

⚙️ CodeRabbit configuration file

**/*: For each subsequent commit in this PR, explicitly verify if previous review comments have been resolved

Files:

  • README.md
  • docs/architecture.md
  • backend/mpt_usage_reporting_extension/persistence/postgres/connection.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/auth.py
  • backend/tests/services/test_charges.py
  • AGENTS.md
  • backend/tests/cli/commands/test_push_estimates_by_id.py
  • backend/mpt_usage_reporting_extension/mpt_client.py
  • docs/error-handling.md
  • backend/tests/services/test_bucket_delete.py
  • backend/mpt_usage_reporting_extension/services/execution_tracker.py
  • backend/tests/test_mpt_client.py
  • backend/mpt_usage_reporting_extension/exceptions.py
  • backend/tests/services/test_statements.py
  • backend/tests/persistence/postgres/test_auth.py
  • backend/tests/persistence/postgres/test_connection.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/insights.py
  • backend/tests/persistence/postgres/test_database.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/database.py
docs/**/*.md

📄 CodeRabbit inference engine (docs/documentation.md)

Topic-specific behavior must live in the matching file under docs/ directory

Follow shared documentation standard for structure and naming in repository documentation

Files:

  • docs/architecture.md
  • docs/error-handling.md
docs/*.md

📄 CodeRabbit inference engine (docs/local-development.md)

Do not duplicate the deployment parameter reference outside docs/deployment.md; link to that document instead.

Files:

  • docs/architecture.md
  • docs/error-handling.md
docs/{architecture,local-development,deployment,contributing,testing,error-handling,migrations,documentation}.md

📄 CodeRabbit inference engine (AGENTS.md)

When applicable, read the relevant repository documentation before proceeding, following the prescribed order: architecture, local development, deployment, contributing, testing, error handling, migrations, and documentation.

Files:

  • docs/architecture.md
  • docs/error-handling.md
docs/**

⚙️ CodeRabbit configuration file

docs/**: Review documentation changes against docs/documentation.md and the linked repository's standards/documentation.md.
Use those documents as the source of truth for structure, topic boundaries, navigation updates, and when to link shared rules instead of copying them.

Files:

  • docs/architecture.md
  • docs/error-handling.md
backend/**/*.py

⚙️ CodeRabbit configuration file

backend/**/*.py: Follow the linting rules defined in backend/pyproject.toml under [tool.ruff] and [tool.flake8].
For formatting, use Ruff instead of Black. Do not suggest Black formatting changes.
Review code against the linked repository's standards/python-coding.md.
Flag any code artifact not written in English: identifiers, comments, docstrings, log messages, error messages, or test names in any other language must be reported and translated to English.
Flag module-level docstrings in __init__.py files, and redundant module-level docstrings that only restate the module name or path.
Verify modules are organized into cohesive packages instead of flat or grab-bag utils/helpers modules, and flag inline linter or type-checker ignores (# noqa, # type: ignore) that are not a narrow, justified last resort.

Files:

  • backend/mpt_usage_reporting_extension/persistence/postgres/connection.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/auth.py
  • backend/tests/services/test_charges.py
  • backend/tests/cli/commands/test_push_estimates_by_id.py
  • backend/mpt_usage_reporting_extension/mpt_client.py
  • backend/tests/services/test_bucket_delete.py
  • backend/mpt_usage_reporting_extension/services/execution_tracker.py
  • backend/tests/test_mpt_client.py
  • backend/mpt_usage_reporting_extension/exceptions.py
  • backend/tests/services/test_statements.py
  • backend/tests/persistence/postgres/test_auth.py
  • backend/tests/persistence/postgres/test_connection.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/insights.py
  • backend/tests/persistence/postgres/test_database.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/database.py
backend/**

⚙️ CodeRabbit configuration file

backend/**: Review backend changes against AGENTS.md, docs/architecture.md, docs/contributing.md, and docs/testing.md.
Use relevant linked shared standards and operational guidance when those local documents reference them.
If the change adds or alters behaviour, components, configuration, or commands, verify the corresponding documentation (docs/*, README.md, AGENTS.md) is updated per standards/documentation.md.

Files:

  • backend/mpt_usage_reporting_extension/persistence/postgres/connection.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/auth.py
  • backend/tests/services/test_charges.py
  • backend/tests/cli/commands/test_push_estimates_by_id.py
  • backend/mpt_usage_reporting_extension/mpt_client.py
  • backend/tests/services/test_bucket_delete.py
  • backend/mpt_usage_reporting_extension/services/execution_tracker.py
  • backend/tests/test_mpt_client.py
  • backend/mpt_usage_reporting_extension/exceptions.py
  • backend/tests/services/test_statements.py
  • backend/tests/persistence/postgres/test_auth.py
  • backend/tests/persistence/postgres/test_connection.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/insights.py
  • backend/tests/persistence/postgres/test_database.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/database.py
backend/tests/**

📄 CodeRabbit inference engine (AGENTS.md)

Inspect the backend test suite when the task concerns backend code or tests.

Files:

  • backend/tests/services/test_charges.py
  • backend/tests/cli/commands/test_push_estimates_by_id.py
  • backend/tests/services/test_bucket_delete.py
  • backend/tests/test_mpt_client.py
  • backend/tests/services/test_statements.py
  • backend/tests/persistence/postgres/test_auth.py
  • backend/tests/persistence/postgres/test_connection.py
  • backend/tests/persistence/postgres/test_database.py

⚙️ CodeRabbit configuration file

backend/tests/**: Review backend test changes against docs/testing.md and the linked repository's standards/unittests.md.
Verify that repository-specific test behavior and shared unit-test rules are followed.
Verify tests are written as functions (not classes), are grouped into packages mirroring the source, share setup through fixtures (splitting a large conftest into a fixtures package registered via pytest_plugins), keep fixture dependency depth at 3 or fewer levels, and use freezegun instead of patching datetime.

Files:

  • backend/tests/services/test_charges.py
  • backend/tests/cli/commands/test_push_estimates_by_id.py
  • backend/tests/services/test_bucket_delete.py
  • backend/tests/test_mpt_client.py
  • backend/tests/services/test_statements.py
  • backend/tests/persistence/postgres/test_auth.py
  • backend/tests/persistence/postgres/test_connection.py
  • backend/tests/persistence/postgres/test_database.py
AGENTS.md

📄 CodeRabbit inference engine (AGENTS.md)

AGENTS.md: For every task, identify the task type and select only the local repository files relevant to that task.
Read only the selected relevant local files before making changes.
If selected local files reference relevant shared standards or operational guidance, read those shared documents before proceeding.
Treat repository-local documents as repository-specific additions, restrictions, or overrides to shared guidance.
When repository-local rules conflict with shared rules, the repository-local rule takes precedence.

Files:

  • AGENTS.md
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: softwareone-platform/mpt-usage-reporting-extension

Timestamp: 2026-07-31T13:41:21.212Z
Learning: Prefer documented make targets over ad hoc Docker commands.
Learnt from: CR
Repo: softwareone-platform/mpt-usage-reporting-extension

Timestamp: 2026-07-31T13:41:21.212Z
Learning: Treat Docker as the default local execution model for the repository.
Learnt from: CR
Repo: softwareone-platform/mpt-usage-reporting-extension

Timestamp: 2026-07-31T13:41:21.212Z
Learning: For shared meaning of common make targets and validation flow, prefer shared knowledge documents instead of inferring semantics from target names alone.
Learnt from: CR
Repo: softwareone-platform/mpt-usage-reporting-extension

Timestamp: 2026-07-31T13:41:33.066Z
Learning: Keep the extension app bare: `app.py` should instantiate `ExtensionApp` without registering custom event, API, or plug routes.
Learnt from: CR
Repo: softwareone-platform/mpt-usage-reporting-extension

Timestamp: 2026-07-31T13:41:42.460Z
Learning: Do not add automatic retries. Estimate uploads may be re-pushed because they use absolute `PUT`s; do not re-run a failed `run` for the same window because additive accumulation can double-count—use `recalculate` instead.
🪛 LanguageTool
AGENTS.md

[style] ~19-~19: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ....md) before changing code or tests. 7. [docs/error-handling.md](docs/error-handling....

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~20-~20: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ons, retries, or failure reporting. 8. docs/migrations.md when...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~21-~21: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...mentions schema or data migrations. 9. [docs/documentation.md](docs/documentation.md...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

docs/error-handling.md

[locale-violation] ~81-~81: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...tacktrace; the exception is re-raised afterwards, so the process still exits non-zero. -...

(AFTERWARDS_US)

🔇 Additional comments (18)
AGENTS.md (1)

19-21: LGTM!

README.md (1)

45-45: LGTM!

docs/architecture.md (1)

132-132: LGTM!

backend/mpt_usage_reporting_extension/mpt_client.py (1)

5-6: LGTM!

Also applies to: 17-17

backend/mpt_usage_reporting_extension/persistence/postgres/insights.py (1)

8-8: LGTM!

Also applies to: 38-38

backend/tests/test_mpt_client.py (1)

4-4: LGTM!

Also applies to: 23-23

backend/tests/services/test_bucket_delete.py (1)

128-133: LGTM!

backend/tests/services/test_charges.py (1)

135-141: LGTM!

backend/tests/services/test_statements.py (1)

123-123: LGTM!

Also applies to: 135-135

backend/mpt_usage_reporting_extension/exceptions.py (1)

1-9: 🗄️ Data Integrity & Integration

Comment is incorrect; no compatibility issue exists.

Production code catches exceptions using either except Exception (pipeline.py, execution_tracker.py, estimates_uploader.py) or specific custom types (e.g., MPTError, UpstreamStatementError). No production code catches RuntimeError, so changing the inheritance of ConfigurationError and UpstreamAPIError to inherit from ExtensionError does not break any existing callers. The new hierarchy is documented in docs/error-handling.md and fully compatible with existing exception handlers.

			> Likely an incorrect or invalid review comment.
backend/mpt_usage_reporting_extension/persistence/postgres/auth.py (1)

9-9: LGTM!

Also applies to: 75-77

backend/mpt_usage_reporting_extension/persistence/postgres/connection.py (1)

5-6: LGTM!

Also applies to: 44-44

backend/mpt_usage_reporting_extension/persistence/postgres/database.py (2)

7-7: LGTM!


25-27: 🩺 Stability & Availability

The exception contract is consistent. ConfigurationError derives from ExtensionError. All database callers use the new exception types, and production code has no RuntimeError dependency. The remaining RuntimeError tests cover unrelated injected failures.

backend/tests/persistence/postgres/test_auth.py (1)

5-5: LGTM!

Also applies to: 87-93

backend/tests/persistence/postgres/test_connection.py (1)

3-3: LGTM!

Also applies to: 51-51

backend/tests/persistence/postgres/test_database.py (1)

7-7: LGTM!

Also applies to: 42-50, 140-140, 154-154, 166-166

backend/tests/cli/commands/test_push_estimates_by_id.py (1)

128-133: LGTM!

try:
yield execution
except BaseException as exc: # noqa: WPS424 (includes typer.Exit)
except Exception as exc:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 'CancelledError|KeyboardInterrupt|typer\.Exit' backend/tests

Repository: softwareone-platform/mpt-usage-reporting-extension

Length of output: 2631


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- execution_tracker.py outline ---'
ast-grep outline backend/mpt_usage_reporting_extension/services/execution_tracker.py || true

printf '%s\n' '--- execution_tracker.py ---'
cat -n backend/mpt_usage_reporting_extension/services/execution_tracker.py

printf '%s\n' '--- focused tests and usages ---'
rg -n -C 6 'ExecutionTracker|execution_tracker|\.track\(|\.record\(' backend/tests backend/mpt_usage_reporting_extension docs README.md AGENTS.md 2>/dev/null || true

printf '%s\n' '--- exception guidance ---'
if [ -f docs/error-handling.md ]; then
  cat -n docs/error-handling.md
fi
if [ -f AGENTS.md ]; then
  cat -n AGENTS.md
fi

printf '%s\n' '--- tracked test files ---'
git ls-files 'backend/tests/*execution*' 'backend/tests/*tracker*' 'backend/tests/*pipeline*'

Repository: softwareone-platform/mpt-usage-reporting-extension

Length of output: 36014


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- focused tracker tests ---'
cat -n backend/tests/services/test_execution_tracker.py

printf '%s\n' '--- pipeline tracking implementation ---'
sed -n '250,325p' backend/mpt_usage_reporting_extension/pipeline.py

printf '%s\n' '--- pipeline exception and typer.Exit tests ---'
rg -n -C 12 'def _tracked|typer\.Exit|notify_failure|CancelledError|KeyboardInterrupt|Exception' backend/mpt_usage_reporting_extension/pipeline.py backend/tests/test_pipeline.py

printf '%s\n' '--- current change summary ---'
git status --short
git diff --stat
git diff -- backend/mpt_usage_reporting_extension/services/execution_tracker.py backend/tests/services/test_execution_tracker.py docs/error-handling.md

Repository: softwareone-platform/mpt-usage-reporting-extension

Length of output: 18338


🏁 Script executed on selected repositories:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 '^(name = "typer"|name = "click"|typer|click)' backend/pyproject.toml backend/uv.lock 2>/dev/null || true

printf '%s\n' '--- local documentation guidance ---'
for file in docs/documentation.md docs/testing.md docs/contributing.md standards/documentation.md; do
  if [ -f "$file" ]; then
    printf '\n--- %s ---\n' "$file"
    cat -n "$file"
  fi
done

printf '%s\n' '--- shared error-handling guidance ---'
rg -n -C 8 'CancelledError|KeyboardInterrupt|BaseException|typer\.Exit|cancellation|termination' standards docs backend 2>/dev/null || true

printf '%s\n' '--- related repository standards ---'
rg -n -C 8 'CancelledError|KeyboardInterrupt|BaseException|typer\.Exit|cancellation|termination' . 2>/dev/null || true

printf '%s\n' '--- recent commits ---'
git log --oneline -8

Repositories: softwareone-platform/mpt-usage-reporting-extension, softwareone-platform/mpt-extension-skills

Length of output: 57064


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import asyncio
import ast

print("asyncio.CancelledError MRO:", asyncio.CancelledError.__mro__)
print("KeyboardInterrupt MRO:", KeyboardInterrupt.__mro__)

try:
    import typer
except ImportError as exc:
    print("typer unavailable:", exc)
else:
    print("typer.Exit MRO:", typer.Exit.__mro__)

`@__import__`("contextlib").asynccontextmanager
async def exception_only_tracker(events):
    events.append("start")
    try:
        yield
    except Exception as exc:
        events.append(("finish", type(exc).__name__))
        raise
    else:
        events.append("success")

async def probe(exc):
    events = []
    try:
        async with exception_only_tracker(events):
            raise exc
    except BaseException as caught:
        return type(caught).__name__, events

async def main():
    for exc in (ValueError("ordinary"), asyncio.CancelledError(), KeyboardInterrupt()):
        print(type(exc).__name__, await probe(exc))

asyncio.run(main())
PY

Length of output: 540


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import asyncio
import ast
from contextlib import asynccontextmanager

print("asyncio.CancelledError MRO:", asyncio.CancelledError.__mro__)
print("KeyboardInterrupt MRO:", KeyboardInterrupt.__mro__)

try:
    import typer
except ImportError as exc:
    print("typer unavailable:", exc)
else:
    print("typer.Exit MRO:", typer.Exit.__mro__)

`@asynccontextmanager`
async def exception_only_tracker(events):
    events.append("start")
    try:
        yield
    except Exception as exc:
        events.append(("finish", type(exc).__name__))
        raise
    else:
        events.append("success")

async def probe(exc):
    events = []
    try:
        async with exception_only_tracker(events):
            raise exc
    except BaseException as caught:
        return type(caught).__name__, events

async def main():
    for exc in (ValueError("ordinary"), asyncio.CancelledError(), KeyboardInterrupt()):
        print(type(exc).__name__, await probe(exc))

asyncio.run(main())
PY

Length of output: 540


Document the Exception boundary and cover termination signals.

Update both tracker docstrings and docs/error-handling.md to state that only escaping Exception subclasses finalize rows as failed. Add tests for asyncio.CancelledError and KeyboardInterrupt in both context managers. Existing tests cover ordinary exceptions and typer.Exit(code=1).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/mpt_usage_reporting_extension/services/execution_tracker.py` at line
45, Update both tracker context-manager docstrings and docs/error-handling.md to
document that only escaping Exception subclasses finalize rows as failed. Add
coverage for asyncio.CancelledError and KeyboardInterrupt in both
context-manager test suites, while preserving existing ordinary-exception and
typer.Exit(code=1) behavior.

Sources: Coding guidelines, Path instructions

Comment thread docs/error-handling.md Outdated
Root all package exceptions in a single ExtensionError base and raise
ConfigurationError for missing or invalid configuration instead of bare
RuntimeError. Narrow the execution-tracker catches from BaseException to
Exception, and drop the duplicate warning logs at the MPT API translation
sites so each failure is reported once, at the boundary that handles it.
Document the exception hierarchy, runtime error flow, and Teams
notification triggers in docs/error-handling.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@albertsola
albertsola force-pushed the refactor/improve-error-handling branch from 9046c46 to 40053f4 Compare July 31, 2026 16:14
@albertsola albertsola changed the title MPT-23854 Align error handling with the shared standard MPT-23854 Improve error handling Jul 31, 2026
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/tests/services/test_execution_tracker.py (1)

50-55: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the original typer.Exit code.

pytest.raises(typer.Exit) checks only the exception type. Capture the exception and assert that its exit code remains 1. This protects the re-raise behavior from a future replacement with a default-code exit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/services/test_execution_tracker.py` around lines 50 - 55,
Update test_track_finishes_failed_on_typer_exit to capture the raised typer.Exit
exception with pytest.raises and assert its exit code is 1, while preserving the
existing execution-status assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/error-handling.md`:
- Around line 84-86: Update ExecutionNotifier.notify_failure to sanitize error
and stacktrace details before constructing the Adaptive Card and calling
send_card, removing secrets, customer identifiers, SQL, tokens, and local paths
while preserving useful failure context. Do not send raw exception diagnostics
to Teams.

---

Nitpick comments:
In `@backend/tests/services/test_execution_tracker.py`:
- Around line 50-55: Update test_track_finishes_failed_on_typer_exit to capture
the raised typer.Exit exception with pytest.raises and assert its exit code is
1, while preserving the existing execution-status assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 719f9b25-0eef-4088-b2ba-f7677cbfe4b3

📥 Commits

Reviewing files that changed from the base of the PR and between 9046c46 and 40053f4.

📒 Files selected for processing (24)
  • AGENTS.md
  • README.md
  • backend/mpt_usage_reporting_extension/cli/commands/push_estimates_by_id.py
  • backend/mpt_usage_reporting_extension/exceptions.py
  • backend/mpt_usage_reporting_extension/mpt_client.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/auth.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/connection.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/database.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/insights.py
  • backend/mpt_usage_reporting_extension/services/bucket_delete.py
  • backend/mpt_usage_reporting_extension/services/charges.py
  • backend/mpt_usage_reporting_extension/services/execution_tracker.py
  • backend/mpt_usage_reporting_extension/services/statements.py
  • backend/tests/cli/commands/test_push_estimates_by_id.py
  • backend/tests/persistence/postgres/test_auth.py
  • backend/tests/persistence/postgres/test_connection.py
  • backend/tests/persistence/postgres/test_database.py
  • backend/tests/services/test_bucket_delete.py
  • backend/tests/services/test_charges.py
  • backend/tests/services/test_execution_tracker.py
  • backend/tests/services/test_statements.py
  • backend/tests/test_mpt_client.py
  • docs/architecture.md
  • docs/error-handling.md
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • softwareone-platform/mpt-extension-skills (manual)
💤 Files with no reviewable changes (4)
  • backend/mpt_usage_reporting_extension/services/statements.py
  • backend/mpt_usage_reporting_extension/cli/commands/push_estimates_by_id.py
  • backend/mpt_usage_reporting_extension/services/bucket_delete.py
  • backend/mpt_usage_reporting_extension/services/charges.py
🚧 Files skipped from review as they are similar to previous changes (17)
  • backend/mpt_usage_reporting_extension/persistence/postgres/connection.py
  • backend/mpt_usage_reporting_extension/exceptions.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/insights.py
  • backend/tests/persistence/postgres/test_connection.py
  • backend/tests/services/test_charges.py
  • backend/tests/cli/commands/test_push_estimates_by_id.py
  • backend/tests/services/test_bucket_delete.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/auth.py
  • backend/tests/services/test_statements.py
  • backend/mpt_usage_reporting_extension/persistence/postgres/database.py
  • backend/tests/test_mpt_client.py
  • backend/mpt_usage_reporting_extension/services/execution_tracker.py
  • backend/mpt_usage_reporting_extension/mpt_client.py
  • README.md
  • backend/tests/persistence/postgres/test_database.py
  • backend/tests/persistence/postgres/test_auth.py
  • docs/architecture.md
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: mpt-extension-usage-reporting (Build Build Image)
  • GitHub Check: mpt-extension-usage-reporting (Prerequisites Create standard build artifact)
  • GitHub Check: mpt-extension-usage-reporting (Prerequisites Set the version)
  • GitHub Check: build
🧰 Additional context used
📓 Path-based instructions (7)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: For each task, identify its type and select only the local repository files relevant to that task before making changes.
Read selected local files before making changes, and read any referenced shared standards or operational guidance relevant to the task.
Treat repository-local documents as additions, restrictions, or overrides to shared guidance; local rules take precedence when they conflict with shared rules.
When applicable, read repository documentation in the prescribed order, beginning with README.md and then the relevant architecture, development, deployment, contributing, testing, error-handling, migration, or documentation guides.
Inspect code paths relevant to the task, including the backend application entry point, backend configuration, migrations, tests, make commands, container files, and CI workflow.
Prefer documented make targets over ad hoc Docker commands.
Treat Docker as the default local execution model for the repository.
For shared meaning of common make targets and validation flow, prefer shared knowledge documents instead of inferring local semantics from target names alone.

Files:

  • backend/tests/services/test_execution_tracker.py
  • AGENTS.md
  • docs/error-handling.md

⚙️ CodeRabbit configuration file

**/*: For each subsequent commit in this PR, explicitly verify if previous review comments have been resolved

Files:

  • backend/tests/services/test_execution_tracker.py
  • AGENTS.md
  • docs/error-handling.md
backend/**/*.py

⚙️ CodeRabbit configuration file

backend/**/*.py: Follow the linting rules defined in backend/pyproject.toml under [tool.ruff] and [tool.flake8].
For formatting, use Ruff instead of Black. Do not suggest Black formatting changes.
Review code against the linked repository's standards/python-coding.md.
Flag any code artifact not written in English: identifiers, comments, docstrings, log messages, error messages, or test names in any other language must be reported and translated to English.
Flag module-level docstrings in __init__.py files, and redundant module-level docstrings that only restate the module name or path.
Verify modules are organized into cohesive packages instead of flat or grab-bag utils/helpers modules, and flag inline linter or type-checker ignores (# noqa, # type: ignore) that are not a narrow, justified last resort.

Files:

  • backend/tests/services/test_execution_tracker.py
backend/**

⚙️ CodeRabbit configuration file

backend/**: Review backend changes against AGENTS.md, docs/architecture.md, docs/contributing.md, and docs/testing.md.
Use relevant linked shared standards and operational guidance when those local documents reference them.
If the change adds or alters behaviour, components, configuration, or commands, verify the corresponding documentation (docs/*, README.md, AGENTS.md) is updated per standards/documentation.md.

Files:

  • backend/tests/services/test_execution_tracker.py
backend/tests/**

⚙️ CodeRabbit configuration file

backend/tests/**: Review backend test changes against docs/testing.md and the linked repository's standards/unittests.md.
Verify that repository-specific test behavior and shared unit-test rules are followed.
Verify tests are written as functions (not classes), are grouped into packages mirroring the source, share setup through fixtures (splitting a large conftest into a fixtures package registered via pytest_plugins), keep fixture dependency depth at 3 or fewer levels, and use freezegun instead of patching datetime.

Files:

  • backend/tests/services/test_execution_tracker.py
docs/**/*.md

📄 CodeRabbit inference engine (docs/documentation.md)

Topic-specific behavior must live in the matching file under docs/ directory

Follow shared documentation standard for structure and naming in repository documentation

Files:

  • docs/error-handling.md
docs/*.md

📄 CodeRabbit inference engine (docs/local-development.md)

Do not duplicate the deployment parameter reference outside docs/deployment.md; link to that document instead.

Files:

  • docs/error-handling.md
docs/**

⚙️ CodeRabbit configuration file

docs/**: Review documentation changes against docs/documentation.md and the linked repository's standards/documentation.md.
Use those documents as the source of truth for structure, topic boundaries, navigation updates, and when to link shared rules instead of copying them.

Files:

  • docs/error-handling.md
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: softwareone-platform/mpt-usage-reporting-extension

Timestamp: 2026-07-31T16:15:18.910Z
Learning: Keep the product as a CLI batch job that selects billing statements, accumulates usage by subscription/agreement and month, persists totals to PostgreSQL, and pushes price estimates back to subscriptions.
Learnt from: CR
Repo: softwareone-platform/mpt-usage-reporting-extension

Timestamp: 2026-07-31T16:15:30.564Z
Learning: After a failed `run`, do not re-run the same window because additive accumulation upserts can double-count; use `recalculate`, which deletes the scope's buckets before refilling them.
🪛 LanguageTool
AGENTS.md

[style] ~19-~19: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ....md) before changing code or tests. 7. [docs/error-handling.md](docs/error-handling....

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~20-~20: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ons, retries, or failure reporting. 8. docs/migrations.md when...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~21-~21: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...mentions schema or data migrations. 9. [docs/documentation.md](docs/documentation.md...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

docs/error-handling.md

[grammar] ~68-~68: Ensure spelling is correct
Context: .... Only escaping Exception subclasses finalise rows as failed. BaseExceptions such a...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[locale-violation] ~86-~86: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...tacktrace; the exception is re-raised afterwards, so the process still exits non-zero. -...

(AFTERWARDS_US)

🔇 Additional comments (5)
AGENTS.md (1)

19-21: LGTM!

docs/error-handling.md (1)

1-83: LGTM!

Also applies to: 87-116

backend/tests/services/test_execution_tracker.py (3)

1-4: LGTM!


58-66: LGTM!


82-88: LGTM!

Comment thread docs/error-handling.md
Comment on lines +84 to +86
- **Failure card (💣), unhandled exception** — an exception escaped the
command body. Includes the error message and the full stacktrace; the
exception is re-raised afterwards, so the process still exits non-zero.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 \
  'stacktrace|traceback|ExecutionNotifier|webhook|MPT_TEAMS|logger\.exception' \
  backend/mpt_usage_reporting_extension/services/execution_notifier.py \
  backend/mpt_usage_reporting_extension/pipeline.py

Repository: softwareone-platform/mpt-usage-reporting-extension

Length of output: 9982


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- notifier implementation ---'
cat -n backend/mpt_usage_reporting_extension/services/execution_notifier.py
printf '%s\n' '--- pipeline failure path ---'
sed -n '240,290p' backend/mpt_usage_reporting_extension/pipeline.py
printf '%s\n' '--- repository references to notifier and redaction ---'
rg -n -i -C 3 'AsyncTeamsNotifications|notify_failure|redact|sanitize|stacktrace|traceback|webhook' .
printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 'mpt-extension-sdk|teams|notification' pyproject.toml poetry.lock requirements*.txt uv.lock 2>/dev/null || true

Repository: softwareone-platform/mpt-usage-reporting-extension

Length of output: 36208


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -i -C 4 'AsyncTeamsNotifications|redact|sanitize|stacktrace|traceback|webhook|send_card' .

Repository: softwareone-platform/mpt-extension-skills

Length of output: 50399


🌐 Web query:

"mpt-extension-contrib-custom-notifications" "AsyncTeamsNotifications"

💡 Result:

AsyncTeamsNotifications is a component provided by the mpt-extension-contrib-custom-notifications library, which facilitates sending notifications from SoftwareONE MPT (Managed Platform Tooling) extensions to Microsoft Teams [1]. Key details regarding AsyncTeamsNotifications include: Purpose: It is designed to send Adaptive Cards to a Microsoft Teams channel asynchronously using httpx.AsyncClient [1]. This approach ensures that notification operations do not block the application's event loop [1]. Location: It is located within the mpt_extension_contrib.custom_notifications.channels.teams_async module [1]. Usage: It acts as an asynchronous counterpart to the synchronous TeamsNotifications class [1]. To implement or resolve it, developers typically work with the AsyncTeamsNotifier base class, which is used to resolve the appropriate notification handler via the notification registry (e.g., ctx.notifications.get(AsyncTeamsNotifier)) [1]. The mpt-extension-contrib-custom-notifications library provides a unified registry (ctx.notifications) that allows MPT extensions to report to various notification channels without needing to re-implement sender logic for each extension [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- tracked dependency files ---'
git ls-files | rg '(^|/)(pyproject\.toml|poetry\.lock|uv\.lock|requirements[^/]*|setup\.cfg|setup\.py|Pipfile)$' || true
printf '%s\n' '--- package metadata references ---'
rg -n -i -C 3 'mpt-extension-contrib-custom-notifications|custom-notifications|mpt-extension-contrib' --glob '!docs/**' --glob '!backend/tests/**' .
printf '%s\n' '--- notifier tests around raw content ---'
sed -n '60,105p' backend/tests/services/test_execution_notifier.py

Repository: softwareone-platform/mpt-usage-reporting-extension

Length of output: 4208


Redact failure details before sending them to Teams.

ExecutionNotifier.notify_failure inserts raw error and stacktrace values into the Adaptive Card. Redact secrets, customer identifiers, SQL, tokens, and local paths before send_card, or document that Teams receives unsanitized diagnostics.

🧰 Tools
🪛 LanguageTool

[locale-violation] ~86-~86: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...tacktrace; the exception is re-raised afterwards, so the process still exits non-zero. -...

(AFTERWARDS_US)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/error-handling.md` around lines 84 - 86, Update
ExecutionNotifier.notify_failure to sanitize error and stacktrace details before
constructing the Adaptive Card and calling send_card, removing secrets, customer
identifiers, SQL, tokens, and local paths while preserving useful failure
context. Do not send raw exception diagnostics to Teams.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant