Repository navigation
1.9.79: report losers and winners fail on a payout they cannot read, and every rows command returns its encoding error - #321
Conversation
…and every rows command returns its encoding error p202 report losers and report winners (and the MCP tools report_losers and report_winners) read the payout of --aff-campaign-id with `payout, _ = fetchCampaignPayout(...)`. A 5xx, a key or role the server refused, a campaign that does not exist or an unreachable server priced every click at a payout of 0: losers listed only the rows that never converted, winners listed rows whose CPC is past their break-even, and both exited 0 (CLAUDE.md #1). The read's error is the command's now, wrapped with %w so it keeps the API error's category, exit code and class-wide hint, and a 404 names `p202 campaign list`. Nothing is printed and the breakdown is not read. fetchCampaignPayout also read an answer with no aff_campaign_payout as 0, for every caller (report breakeven and campaign optimize too), and returned its JSON parse error uncategorized (exit 1, the generic --help hint). Both are a server error now (exit 4) naming `p202 campaign get <id> --json`. The column is DECIMAL NOT NULL: a payout of 0 is still 0, and a number (1.9.77 on) and a numeric string (before) read the same. rowsToJSON printed its json.Marshal error and returned nil, so a row JSON cannot carry left stdout empty and the command exited 0. It returns the error (server, exit 4, with a hint), and each of its ten call sites passes it up, through renderRows where the rows are the last thing printed; losers and winners encode before printing their notes, so a failure leaves stderr the error alone. The test fakes answered the payout as the old string ("0", "160") or not at all (masked_test); they answer it as the API does now, a number. TestEncodingErrorsReachTheCaller is the floor for the encoding shape: errcheck does not report a blank assignment (golangci-lint ran clean with `out, _ := rowsToJSON(...)` planted), so the test walks the CLI's source for an encoding call's error assigned to _ and for a function that makes one and returns no error, listing the 18 that remain with what each does on a failure. The fetchCampaignPayout entry leaves requestErrorDrops. Stacked on #310. Backlog: WHC2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Brings in 1.9.79's new commits. 1.9.79 added pageJSON, which encodes a page of rows with truncated, limit and offset beside them, for report breakeven, losers and winners. Like rowsToJSON before this branch, it printed an encoding failure and answered nil. It now returns that failure as the command's error, as rowsToJSON does on this branch, and rowsToJSON is pageJSON with nothing beside the rows. losers and winners keep 1.9.79's payout_net sort and truncated note, which now come after the encoding, so a failure leaves stderr holding only the error. The help texts carry both sides' sentences. The changelogs and the CLI table keep 1.9.79's lines, with this branch's after them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, changelogs.txt, where both sides added lines: the incoming lines are kept as they are and this branch's follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
|
1.9.79 moved to c6d5167: every other open 1.9.79 PR is now merged into it. This PR is not, because it conflicts with the paged/truncated report work that landed (go-cli/cmd/report_optimize.go: |
1.9.79's PHPStan job has been red since the merges at 15:46Z. #308 extended ForbidNumericCastOfRequestParamRule to the pages and baselined the casts already there. Three PRs merged beside it each added a cast that its baseline does not count, and each PR passed on its own: - Personal settings (#283) cast the same-IP fallback after checking it is a key of its choices. It now stores 1 for '1' and 0 for '0', the two keys the check admits. - Setup > Offline Conversions (#293) cast a feed id after a regex test. It now reads it with PostedId::read(). - Setup > Traffic Sources (#271) cast the edited account's id to read its stored postbacks. The page's own ownership check compares the raw value in SQL, where MySQL reads '12abc' as 12. So a save from ?edit_ppc_account_id=12abc changed account 12 (one of the account's own, not the one named) and held that account's postbacks. Measured on a live instance: the old page saved the tampered edit onto account 1. The id is now read exactly, and anything else is refused in the page's words before anything is read or written. The baseline counts are unchanged: each file now holds only the casts they counted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…rs-returned 1.9.79's PHPStan job is red on its own tip: three request casts that its merges left. #328 fixes them, and it is merged here so this branch's PHPStan check runs green. When #328 merges into 1.9.79, the commit is already there and this merge adds nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
|
Updated from 1.9.79, as you asked. 52af15c merges c6d5167 and resolves the clash in
What is still red, and why it is not this PR's:
Generated by Claude Code |
Stacked on #310 (
1.9.79-optimize-offers-error, which is itself stacked on #297). This PR must merge after #310. Until then its diff also shows #310's commits (and #297's). This PR's own change is the single commit eda5a87.Backlog: WHC2 (a Go-CLI-only follow-up to 6WFB / #310)
What was wrong
report losersandreport winnerswith--aff-campaign-idread the campaign's payout withpayout, _ = fetchCampaignPayout(...). The MCP toolsreport_losersandreport_winnersdo the same. If the read hit a 5xx, a key or role the server refuses, a campaign that does not exist, or an unreachable server, every click was priced at a payout of 0. Losers then listed only the rows that never converted, winners listed rows whose CPC is past break-even, and both exited 0 (CLAUDE.md Replace all <? as <?php. #1).fetchCampaignPayoutread an answer with noaff_campaign_payoutas 0 (toFloat(nil)). This affected every caller: report breakeven and campaign optimize too.--helphint.rowsToJSONprinted its own error. It printed itsjson.Marshalerror and returned nil. Each of its 10 call sites (render(rowsToJSON(rows))) then printed nothing on stdout and exited 0. That breaks the CLI rule "never print errors yourself".What changed (Go CLI only)
fetchCampaignPayout%w, so it keeps the API error's category, exit code and class-wide hint: 5xx gives exit 4 andp202 system health; 403 gives exit 2 and the role or scope hint; a network failure gives exit 3. A 404 gets its own hint namingp202 campaign list.servererror (exit 4) namingp202 campaign get <id> --json:aff_campaign_payout, or a null one (the column isDECIMAL(8,2) NOT NULL)v1.9.77tag, andphp -rgives{"a":160,"b":0}with the API's flags) and a numeric string (older servers).losers / winners
rowsToJSON([]byte, error), where the error is aserverCLIError (exit 4) with a hint.renderRowshelper encodes and renders.report_optimize.go×2verify.go×5 (rotator test/check/trace, tracker test/check)conversion_verify.go(conversion simulate)countries.go(rotator criteria-values)system_cron.go(table mode)p202 report breakdown --json.Note:lines, so on a failure stderr holds the error alone.Tests
report_payout_test.go, against a fake that answersGET /campaigns/7with the full record shape the API sends:exitCodeForError,hintForand the message; that stdout and stderr are empty; that the breakdown is not read; the envelope underrunAsMain; and the MCP tool's exit code.160,"160.00",0and"0.00"(0 still lists only zero-conversion spend as CUT).rowsToJSON: a unit test, plus command-level overflow cases for winners and breakeven that assert nothing is printed, notes included."0","160"), or not at all (masked_test.go), now answer it as the API does: a number (CLAUDE.md Update release notes in README #9, [Update] Prevent warnings in subid deletion #28).requestErrorDrops: thefetchCampaignPayoutentry is removed.cmd/sync.go: orchestrator.Poststays; it is VZ10's.New structural check:
TestEncodingErrorsReachTheCallerout, _ := rowsToJSON(listed)planted, it reported0 issues, because errcheck's check-blank is off._rowsToJSON's old shapejson.Marshal,json.MarshalIndent, or a one-argumentEncodeencodingErrorSinks, each with what it does on a failure. Most re-encode values decoded from JSON, which cannot fail. The list only shrinks.Docs
docs/cli.md: new "Break-even, losers and winners" subsection.documentation/cli/10-go-cli.md: a row for the three commands.--helpLong text of breakeven and losers.changelogs.txtand202-config/changelogs.jsonunder 1.9.79.Verification
.claude/skills/p202-verify/scripts/verify.sh --since origin/1.9.79-optimize-offers-error --changed:The
gotier coversgofmt,go vet ./...,go test ./...and the empty-HOMEgo test ./cmd/.... I also rango vet,go test ./...andgolangci-lint run ./...(0 issues) by hand under the local toolchain.Planted defects. Each was planted, backed up to a
mktemppath, and restored with acmpcheck. Each failed a named test (an assertion failure, not a build error):payout, _ = fetchCampaignPayout(...)back in triage): failsTestPayoutCommandsFailWhenThePayoutCannotBeRead(all 18 losers/winners subtests) andTestRequestErrorsAreNotDiscarded.TestPayoutCommandsFailWhenThePayoutCannotBeRead(the "record without its payout" and "null payout" subtests for all three commands).rowsToJSONerror (out, _ := rowsToJSON(listed)): failsTestRankingsFailOnRowsTheyCannotEncodeandTestEncodingErrorsReachTheCaller. golangci-lint reported0 issueswith this plant in place.rowsToJSONprinting its error and returning nil: failsTestRowsToJSONReturnsItsErrorandTestRankingsFailOnRowsTheyCannotEncode.var b, _ = json.MarshalIndentb, _ := json.Marshal_ = enc.Encode_ = renderRowsout, _ := rowsToJSONencoding/jsonimportrowsToJSONsinkWhat could not run
GET /campaigns/{id}record, with the payout as a number asnumericDecimals()sends it (pinned server-side byControllerTest::testADecimalFieldIsANumberAndTheVersionIsOfWhatIsServed), and error bodies inBootstrap::errorResponse()'s shape.For the reviewer
server/ exit 4, as 1.9.79: campaign optimize fails on a ranking it cannot read, and names the salvage campaign's id #310 chose for campaign optimize's ownjson.Marshalfailure. The only reachable cause is a figure the server sent that is too large to compute with.encodingErrorSinksare not swallows:runBatch (func literal)andeval runCase. The first hands the error on through a captured variable; the second records it as the case's failure. The test reads by name and cannot tell either apart, so they are listed with that reason.Generated by Claude Code