fix(logging): stop the serialization fallback from needing a fallback - #1494
fix(logging): stop the serialization fallback from needing a fallback#1494groupthinking wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 36 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited) Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This is obsolete on arrival, and the #1452 merged as #1491 at 21:00, five minutes before this PR was opened. This branch re-applies the same fallback on top of a Recommend closing. The residual holes that do still exist in the merged fallback are covered in #1497:
All three reproduced against current One process note, since this PR and #1497 were both produced by unattended runs of the same scheduled routine: eight PRs were opened against #1452 within about five minutes (#1471, #1472, #1477, #1488, #1491, #1493, this one, #1497). Concurrent runs are not seeing each other's work, so each independently re-derives the same fix. That is worth fixing at the scheduler before it recurs — it has already exhausted the CodeRabbit review allowance for this repo. Generated by Claude Code |
#1491 closed #1452 by re-serializing from the scalar fields when `json.dumps` fails, so a bad enrichment costs its own value rather than the whole record. One residual path still costs the record. The exception the fallback catches is not `json`'s own error. In the exploding-`__str__` case it is *whatever that* `__str__` *raised*, which is arbitrary caller code. `f"{type(exc).__name__}: {exc}"` calls `str(exc)` on it, so an exception that also raises on `str()` fails inside the handler for the failure — losing the record for exactly the reason the fallback exists to prevent. Measured against main @ 8517bf8: 2 of 3 records reach the sink. `_describe_exception` falls back to the class name, an attribute lookup that runs no user code. Also covers `performance_ms`. The enrichment loop reads two attributes and #1491 tested only `correlation_id`, leaving half the reachable surface untested. That test passes on main — it closes a coverage gap rather than pinning a fix, and is marked as such. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cGXYUfmU6yDSLZWe697eY
f07bed9 to
618a604
Compare
|
@coderabbitai review Requesting explicitly because the auto-review skipped. Please attack two claims specifically:
Unrelated to this PR: the label gate #1425 fixed is still liveFlagging this because this PR is a live instance and the fix is recorded as landed. CodeRabbit skipped auto-review here (Run ID The What is new is that #1425 was supposed to have fixed this, and it is on $ git show origin/main:.coderabbit.yaml | grep -A1 'No label gate'
# No label gate. This is the schema default, set explicitly to override an
# inherited required-labels list from the dashboard/org config …
labels: []This PR's base is
I have not pushed a config change for this, deliberately: CodeRabbit reads The two candidate fixes, for whoever picks this up:
Worth reopening #1424/#1425, or filing fresh — every PR in this repo is still opening without an automated review. Generated by Claude Code |
|
Flagging state rather than requesting a change: the base fix this PR implements merged 20 minutes before this PR opened, as #1491 ( Six PRs currently implement #1452 — #1471, #1472, #1477, #1488, #1493 and this one — all opened within eleven minutes of each other, all after #1491 landed. This is the only one of the six that is not a draft, which is why it gets the comment: it is the only one that could merge as-is. The residual delta here is real and worth keeping. safe["serialization_error"] = f"{type(exc).__name__}: {exc}"An exception whose own It is not the only hole
I have opened #1504 as the union of all three, with No action needed from this branch. Under Generated by Claude Code |
Competing implementation: #1497This PR's handoff says it "was rescoped specifically to avoid being one" — that rescope resolved the collision with #1491, but a second collision opened afterwards. #1497 was created 81 seconds after this PR and is a strict superset of it. Both add
That third point matters for this PR specifically: Merging either makes the other conflict. Recommend consolidating on #1497 and closing this one. #1497's body lists #1471, #1472, #1477 and #1488 as superseded but does not mention this PR — worth adding, since that makes six PRs against #1452. Not closing it here — repo owner's call. No action taken on either PR. Generated by Claude Code |
Correction: the CI note in the body is superseded, and the mechanism is now evidencedThe body says this PR had received no What happened. The force-push to
So the trigger configuration was never the problem — That is consistent with GitHub's documented rule that events raised using the repository Why it still matters even though this PR now has CI. The failure is silent. Absent checks are not listed as absent, so a PR opened this way shows a short, all-green check list and can reach Current state. All 25 checks are The rest of the body's verification section is unaffected: it reports local runs on Generated by Claude Code |
This is a strict subset of #1497, and both leave one hole openScheduled PR-remediation routine — flagging an overlap and a gap rather than asking for a change here. Overlap. #1497 adds a near-identical This PR is non-draft and #1497 is draft, so this one likely merges first and leaves #1497 conflicting. Worth deciding deliberately which survives rather than letting merge order choose. On scope, #1497 is the superset. The gap both share. Neither closes the oversized- Verified end-to-end against current and against #1497's filter transcribed verbatim: So it survives #1491 as merged, this PR, and #1497. The one-branch fix and a regression test are in my comment on #1497; full write-up with reproductions in #1501. Reproduce with Nothing here is wrong: Generated by Claude Code |
Overlaps #1497 — independent branches, same two files#1497 ("close three residual record-loss holes in the JSON fallback") is not stacked on this PR. Verified locally: Both branches change exactly the same two files:
and both independently introduce Consequence: whichever merges first leaves the other conflicted on Given #1497 appears to be a superset, the likely resolution is to merge #1497 alone and close this PR — but that turns on whether #1497's copy of the fallback is identical in behaviour to this one, which is a review call, not a mechanical one. Flagging rather than acting. No action was taken on either PR. Generated by Claude Code |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== PR metadata and changed files =='
gh pr view 1494 --repo groupthinking/EventRelay --json number,headRefName,baseRefName,mergeStateStatus,statusCheckRollup,files
echo
echo '== Diff =='
gh pr diff 1494 --repo groupthinking/EventRelay -- logging_config.py tests/unit/test_logging_config_crlf.py || \
gh pr diff 1494 --repo groupthinking/EventRelay
echo
echo '== Candidate module paths =='
fd -a '^logging_config\.py$|^test_logging_config_crlf\.py$|^\.coderabbit\.yaml$' .
echo
echo '== Python structure =='
module_path="$(fd -a '^logging_config\.py$' . | head -n1)"
test_path="$(fd -a '^test_logging_config_crlf\.py$' . | head -n1)"
if [ -n "$module_path" ]; then
ast-grep outline "$module_path" --items all --view expanded
fi
if [ -n "$test_path" ]; then
ast-grep outline "$test_path" --items all --view expanded
fi
echo
echo '== Relevant symbols and configuration =='
if [ -n "$module_path" ]; then
rg -n -C 12 '\b_describe_exception\b|\b_format_json\b|\bserialization_error\b|\bjson\.dumps\b' "$module_path"
fi
if [ -n "$test_path" ]; then
rg -n -C 10 '\bdescribe_exception\b|\bserialization_error\b|\bstr_raises\b|CWE|hostile|CRLF' "$test_path" || true
fi
config_path="$(fd -a '^\.coderabbit\.yaml$' . | head -n1)"
if [ -n "$config_path" ]; then
cat -n "$config_path"
fiLength of output: 25862 🏁 Script executed: #!/bin/bash
set -euo pipefail
module='src/youtube_extension/backend/config/logging_config.py'
tests='tests/unit/test_logging_config_crlf.py'
echo '== Formatter implementation =='
sed -n '108,205p' "$module"
echo
echo '== Test helpers and relevant tests =='
rg -n -C 18 \
'^def (_make_json_logger|_emit_three|test_exploding_str_correlation_id_does_not_cost_the_record|test_exploding_str_performance_ms_does_not_cost_the_record|test_fallback_survives_an_exception_whose_own_str_raises|test_serialization_fallback_still_escapes_attacker_content)' \
"$tests"
echo
echo '== All exception-description and JSON fallback references =='
rg -n -C 5 '\b_describe_exception\s*\(|\bserialization_error\b|json\.dumps\s*\(' \
"$module" "$tests"
echo
echo '== CodeRabbit repository YAML =='
if [ -f .coderabbit.yaml ]; then
cat -n .coderabbit.yaml
else
fd -H -a '^\.coderabbit\.yaml$' . -x sh -c 'echo "== $1 =="; cat -n "$1"' sh {}
fi
echo
echo '== PR comments mentioning label-gate diagnostics =='
gh pr view 1494 --repo groupthinking/EventRelay --comments \
--json comments \
--jq '.comments[] | select(.body | test("Required labels|Configuration used|inheritance|label gate"; "i")) | {author: .author.login, createdAt, body}'Length of output: 6249 ⚙️ CodeRabbit configuration inspected Length of output: 19172 🏁 Script executed: #!/bin/bash
set -euo pipefail
repo='groupthinking/EventRelay'
ref="$(gh pr view 1494 --repo "$repo" --json headRefOid --jq .headRefOid)"
test_path='tests/unit/test_logging_config_crlf.py'
echo '== Test file at PR head =='
gh api "repos/$repo/contents/$test_path?ref=$ref" --jq .content \
| base64 --decode \
| nl -ba \
| sed -n '1,470p'
echo
echo '== Relevant tests at PR head =='
gh api "repos/$repo/contents/$test_path?ref=$ref" --jq .content \
| base64 --decode \
| rg -n -C 18 \
'test_(exploding_str|fallback_survives|serialization_fallback)|serialization_error|_NastyError|_ExplodingStr|_make_json_logger|_emit_three'
echo
echo '== Base-branch CodeRabbit YAML =='
gh api "repos/$repo/contents/.coderabbit.yaml?ref=main" --jq .content \
| base64 --decode \
| nl -ba \
| sed -n '1,180p'Length of output: 1187 🏁 Script executed: #!/bin/bash
set -euo pipefail
repo='groupthinking/EventRelay'
ref="$(gh pr view 1494 --repo "$repo" --json headRefOid --jq .headRefOid)"
test_path='tests/unit/test_logging_config_crlf.py'
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
gh api "repos/$repo/contents/$test_path?ref=$ref" --jq .content \
| base64 --decode > "$tmp"
echo '== Relevant test definitions =='
rg -n -C 22 \
'^(class _ExplodingStr|def _make_json_logger|def _emit_three|def test_exploding_str|def test_fallback_survives|def test_serialization_fallback)|serialization_error|_NastyError' \
"$tmp"
echo
echo '== Hostile class-name coverage search =='
rg -n -i -C 5 \
'type\(|__name__|class name|dynamic.*class|newline.*class|control.*class|serialization_error' \
"$tmp"
echo
echo '== Base branch CodeRabbit label settings =='
gh api "repos/$repo/contents/.coderabbit.yaml?ref=main" --jq .content \
| base64 --decode \
| rg -n -C 4 'inheritance|labels:|auto_review'Length of output: 14370
Finding — This does not make the bare Use an attribute access that bypasses a custom metaclass override, and test it with an exception class that has a metaclass whose For CWE-117, the degraded The current test on Lines 421-430 verifies hostile content in The label-gate report is confirmed by the effective configuration. ✏️ Learnings added
|
Closed as superseded by #1504 — terminal state
|
Merge-order hazard: this is the weakest of three overlapping PRs, and the only one marked readyAutomated PR-remediation sweep. #1494, #1497 and #1504 all rewrite the same serialization fallback in
return json.dumps(payload, ensure_ascii=True, default=str)
except Exception as exc:
safe = {k: v for k, v in payload.items()
if isinstance(v, (str, int, float, bool, type(None)))}
safe["serialization_error"] = f"{type(exc).__name__}: {exc}"
return json.dumps(safe, ensure_ascii=True, default=str)
The ordering problem: this PR is the only one of the three that is not a draft, i.e. the one queued to land — and it fixes 1 of 3. If it merges first, #1497 and #1504 both conflict on the same lines, and their remaining fixes have to be re-derived by hand on top of it. That is where they get quietly dropped. The forged- Recommendation: land #1504 and close #1494 and #1497 as subsumed. If #1494 is preferred for review size, it should at minimum not merge before #1497/#1504 are rebased onto it. No changes pushed — consolidating across three branches is the author's call. Generated by Claude Code |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
…1508) * fix(review): make the label-gate override actually bind (inheritance: false) #1425 set `reviews.auto_review.labels: []` to clear an inherited required-labels gate that was deadlocking CodeRabbit auto-review against `auto_apply_labels: true`. The gate is still active. Evidence: PRs opened after #1425 landed on main still receive the CodeRabbit commit status "Review skipped: excluded by label configuration" — #1483 (21:02Z) and #1494 (21:10Z), both non-draft, both based on a main that already carried the empty list. That message can only be emitted by a label gate that is still evaluating, so the override was not taking effect. Cause: with `inheritance: true` the org/dashboard config layers underneath this file, and a key set to an empty collection reads as unset rather than as "override with nothing" — so the inherited list survived. `inheritance` is the only in-file lever over that, hence flipping it off. Note the failure mode this restores protection against: auto-label.yml labels a PR by changed path, but it is an Actions job and queues behind whatever else is running. CodeRabbit evaluates the gate on PR open. When the Actions queue is saturated the PR is still unlabelled at that moment, the gate skips it, and — per the comment already in this file — labelling afterwards does not retro-trigger a review. The PR is then silently never reviewed. Trade-off: settings this file leaves unset now fall back to the CodeRabbit schema default instead of the dashboard. The file already sets the review, tooling, labeling, chat, knowledge-base and issue-enrichment blocks explicitly, which is what its header claims it does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017yTj3erqg8xRb79v2djrmH * test(review): pin inheritance to false, with the reason CI caught this: `test_inheritance_enabled` asserts `.coderabbit.yaml`'s `inheritance is True`, which aa31594 changed to false. 1 failed, 8069 passed — a real assertion, not a flake. The test was a snapshot of the values chosen when the CodeRabbit config was version-controlled; its docstring restated the setting rather than defending it. Changing the setting deliberately means the test changes with it, so this flips the assertion and renames it to match. The docstring now carries the reason instead of the restatement. The failure mode is invisible — flipping inheritance back to true does not fail anything, it just silently stops auto-review from ever starting — so the test is the only place that will tell someone why the value is what it is. Verified by replicating the file's config assertions directly; pytest is not installed in this sandbox, so CI is the real check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017yTj3erqg8xRb79v2djrmH --------- Co-authored-by: Claude <noreply@anthropic.com>
Canonical issue
Follow-up to #1452, which #1491 closed while this PR was in flight. Not a competing implementation — this PR is now rebased onto
8517bf8and keeps #1491's design intact. It closes one residual path that fix does not cover.Outcome
_format_json's serialization fallback can still cost the record it exists to save.The exception the fallback catches is not
json's own error. In the exploding-__str__case it is whatever that__str__raised — arbitrary caller code.maininterpolates it directly:f"...{exc}"callsstr(exc). An exception that also raises onstr()therefore fails inside the handler for the failure, andHandler.handleErrordrops the record for exactly the reason the fallback was added to prevent.Measured against
main@8517bf8, threelogger.infocalls with the middle one poisoned:The
<exception str() failed>line is Python's own logging machinery giving up on the record.Scope
logging_config.py—_describe_exception, which falls back totype(exc).__name__(an attribute lookup that runs no user code); the fallback now uses it. 6 lines of behaviour.tests/unit/test_logging_config_crlf.py— 2 tests.except Exception, theserialization_errorkey and its format on the normal path all stay exactly as merged. This only changes how the description is built.%sargument whose__str__raises. That fails inrecord.getMessage()while the payload is still being built, above the try — and fails identically on the line-oriented path, so it is not specific to JSON and not fixable here.JSON_LOGGINGdefault mismatch (production_config.py:72says"true",logging_config.pysays"false"). Still a config decision, same as on fix(security): build JSON log records with json.dumps (CWE-117 field forgery) #1439 and fix(logging): keep the record when JSON serialization fails (#1452) #1491.Risk
try/exceptaround a format string. On every exception with a working__str__— which is all of them in practice — the output is byte-identical tomain's, sotest_exploding_str_correlation_id_does_not_cost_the_record's exact-match assertion ("RuntimeError: str() exploded") still passes unchanged. The degraded form appears only where the alternative today is no record at all.git revert. No migration, config, or schema change.Verification
Head
618a604, rebased on8517bf8. Measured, not inferred.Focused tests —
tests/unit/test_logging_config_crlf.py: 25 passed. Collection goes 23 → 25, so all 23 tests onmain— including fix(logging): keep the record when JSON serialization fails (#1452) #1491's three — are unchanged and still pass.Non-vacuous, and honest about which test proves what. Reverting only
logging_config.pyand re-running:Exactly one test fails against
main, and it is the one pinning the new behaviour. The other new test (test_exploding_str_performance_ms_does_not_cost_the_record) passes onmain— it closes a coverage gap rather than pinning a fix, and both the test's comment and the commit message say so rather than letting it pad a non-vacuity claim.Why the second test earns its place. The enrichment loop reads two attributes; fix(logging): keep the record when JSON serialization fails (#1452) #1491 tested only
correlation_id.performance_msarrives by a different route — set fromrecord.durationinsideformat(), or straight fromextra=— so half the reachable surface had no test.Lint —
ruff checkclean on both changed files.Type checking —
mypyreports 17 errors onmainand 17 on this head, a byte-identical set. All 17 are pre-existing, at lines this PR does not touch.Blast radius —
_describe_exceptionis module-private with exactly one caller._format_jsonis reached only viaStructuredFormatter.formatwhenjson_outputis set, which onlysetup_logging's dictConfig sets.test_logging_config_crlf.pyis the only test file importing the module.Required CI — see the note below; no
pull_request-event workflow has fired on this PR.Review threads resolved — none open.
A CI observation, not a claim about this PR
This PR has received no
pull_request-event workflow runs at all — noCI,Coverage,Security Scan,CodeQL,Secret Scan, orDependency Review. Onlypull_request_targetworkflows (PR Governance,PR Checks) and Vercel appeared, and two of those satqueuedfor the entire run.ci.yml,coverage.ymlandsecurity.ymlall carry an unfilteredpull_request:trigger, so a base-branch filter is not the cause — #1447 removed those. The distinguishing feature is that this PR was opened automatically by repository automation on push, not by a session.ci.yml's own header comment already records a sibling failure mode ("a PR stacked onto another PR's branch ran no CI at all", #1440).I have not diagnosed the mechanism and am not guessing at one. Flagging it because the consequence is concrete: a PR can reach
mergeable_state: cleanhere having run no engineering gate at all, and nothing in the check list makes that visible — the absent checks simply are not listed. The local evidence above is what stands in for CI on this head; it is not a substitute for it.Production evidence
Not applicable as a preview: backend logging, no
apps/web/**surface, which is what gate 4 ofMERGE_POLICY.mdscopes previews to.The runtime evidence is the reproduction itself, run against the real formatter through a real
StreamHandlerin both directions — 2 of 3 records onmain@8517bf8, 3 of 3 on this head.production_config.py:72defaultsJSON_LOGGINGto"true", so the JSON path is live.Severity: low, and narrower than #1452's. Reaching it needs a call site to pass an object whose
__str__raises an exception whose own__str__also raises. No current call site passes a non-scalar at all. This is a correctness gap in a safety net, not an exploitable path.Agent handoff
default=strin_format_jsondoes not deliver its stated guarantee — a record can still be lost (follow-up to #1439) #1452, as a post-merge follow-up to fix(logging): keep the record when JSON serialization fails (#1452) #1491default=strin_format_jsondoes not deliver its stated guarantee — a record can still be lost (follow-up to #1439) #1452's four criteria are met onmainby fix(logging): keep the record when JSON serialization fails (#1452) #1491; this closes the residual path in criterion 2 and criterion 4's "state the guarantee the code actually provides"Agent provenance
Produced by a scheduled, unattended PR-remediation routine running under the repo owner's account. The PR was opened automatically by repository automation on push; the body is the routine's.
Not dispatched through the agent workflow, so it fills in no
agent-lock-manifestand fabricates no pre-dispatch intent snapshot or terminal-agent-result — consistent with the #810/#1270 directive not to weaken or impersonateagent-completion/truth-gate.The branch was force-pushed once, replacing a single unmerged commit of my own (
f07bed9) with the rebased-and-narrowed618a604. No merged history was discarded.Left ready for review rather than draft:
.coderabbit.yamlsetsauto_review.drafts: false, so drafting would suppress the automated review. It halts at the human/governance gate — no auto-merge to protectedmainis requested or performed.