-
Notifications
You must be signed in to change notification settings - Fork 0
fix(webhook): surface push-notification delivery path at INFO (closes #61) #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,17 @@ | |
| # otherwise block anyone from importing tiny helpers (e.g. _build_agent_card) | ||
| # out of this module for tests. Keep the import inside _main(). | ||
|
|
||
| # Root-level log config. Python's default is WARNING, which silently filtered | ||
| # every `logger.info(...)` call — including "push config registered" and | ||
| # "webhook delivered" lines from a2a_handler, making every diagnosis of the | ||
| # A2A/webhook path a guess-and-check session (quinn#61). INFO is the right | ||
| # default for a production agent; LOG_LEVEL env var lets operators tune up | ||
| # or down without a code change. | ||
| logging.basicConfig( | ||
| level=os.environ.get("LOG_LEVEL", "INFO").upper(), | ||
| format="%(asctime)s %(levelname)s %(name)s %(message)s", | ||
|
Comment on lines
+33
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Verify current usage in repo
rg -n 'LOG_LEVEL|basicConfig\(' server.py
# Verify stdlib behavior for valid/invalid level names
python - <<'PY'
import logging
for value in ["INFO", "DEBUG", "INF0", "NOT_A_LEVEL"]:
try:
logging.basicConfig(level=value, force=True)
print(f"{value}: OK")
except Exception as e:
print(f"{value}: ERROR -> {type(e).__name__}: {e}")
PYRepository: protoLabsAI/quinn Length of output: 351 Validate At line 34, an invalid env value (e.g., Proposed fix+_raw_log_level = os.environ.get("LOG_LEVEL", "INFO")
+_resolved_log_level = getattr(logging, _raw_log_level.upper(), None)
+if not isinstance(_resolved_log_level, int):
+ _resolved_log_level = logging.INFO
+
logging.basicConfig(
- level=os.environ.get("LOG_LEVEL", "INFO").upper(),
+ level=_resolved_log_level,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
)🤖 Prompt for AI Agents |
||
| ) | ||
|
|
||
| # Module-level logger — used to surface uncaught exceptions in the LangGraph | ||
| # stream/invoke code with a full traceback. Without this the A2A handler only | ||
| # sees str(e), which drops the frame location and makes every runtime bug a | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -660,6 +660,36 @@ async def test_webhook_delivery_success(): | |
| assert payload["artifact"]["artifactId"] == "test-task-id" | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_webhook_success_is_logged_at_info_level(caplog): | ||
| """Quinn #61 root cause: successful webhook delivery used to log at | ||
| DEBUG, which is below the default WARNING threshold the server runs | ||
| at. There was no way to tell from docker logs whether delivery was | ||
| actually firing. Upgrade locked in — every successful POST must | ||
| produce an INFO line carrying the task id, final state, and URL.""" | ||
| import logging | ||
| record = _make_record(state=COMPLETED) | ||
| cfg = PushNotificationConfig(url="https://example.com/hook") | ||
|
|
||
| with patch("a2a_handler.httpx.AsyncClient") as mock_client_cls: | ||
| mock_client = AsyncMock() | ||
| mock_client.__aenter__ = AsyncMock(return_value=mock_client) | ||
| mock_client.__aexit__ = AsyncMock(return_value=None) | ||
| mock_response = MagicMock() | ||
| mock_response.status_code = 200 | ||
| mock_client.post = AsyncMock(return_value=mock_response) | ||
| mock_client_cls.return_value = mock_client | ||
|
|
||
| with caplog.at_level(logging.INFO, logger="a2a_handler"): | ||
| await _deliver_webhook(record, cfg) | ||
|
|
||
| info_records = [r for r in caplog.records if r.levelno == logging.INFO] | ||
| assert any("webhook delivered" in r.getMessage() for r in info_records), ( | ||
| "successful delivery must log at INFO, not DEBUG — otherwise the " | ||
| "default WARNING log level silently hides whether delivery is working" | ||
| ) | ||
|
Comment on lines
+686
to
+690
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lock the full log contract, not just the prefix. This test currently passes even if task/state/URL details regress. Please assert those required fields explicitly. Proposed test hardening- info_records = [r for r in caplog.records if r.levelno == logging.INFO]
- assert any("webhook delivered" in r.getMessage() for r in info_records), (
+ info_messages = [r.getMessage() for r in caplog.records if r.levelno == logging.INFO]
+ assert any(
+ "webhook delivered" in m
+ and "task=test-task-id" in m
+ and f"state={COMPLETED}" in m
+ and "https://example.com/hook" in m
+ for m in info_messages
+ ), (
"successful delivery must log at INFO, not DEBUG — otherwise the "
"default WARNING log level silently hides whether delivery is working"
)🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_webhook_delivery_no_token(): | ||
| record = _make_record(state=FAILED, error_message="oops") | ||
|
|
@@ -1003,8 +1033,13 @@ async def test_message_send_returns_submitted(): | |
|
|
||
| assert resp.status_code == 200 | ||
| data = resp.json() | ||
| # Spec-compliance: the result must be a full Task object with the | ||
| # `kind` discriminator — @a2a-js/sdk routes by kind. Inline-dict | ||
| # builds have omitted it in the past (quinn#61 investigation). | ||
| assert data["result"]["kind"] == "task" | ||
| assert data["result"]["status"]["state"] == SUBMITTED | ||
| assert "id" in data["result"] | ||
| assert "contextId" in data["result"] | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid logging full webhook URLs at INFO (secret leakage risk).
These logs include full callback URLs. If operators use query tokens or userinfo in URLs, secrets get persisted in logs.
Proposed fix (sanitize URL before logging)
Also applies to: 1215-1217
🤖 Prompt for AI Agents