Repository navigation
Atomic output, CSV formula guard, one paid solve per page, canary names a dead proxy - #21
Merged
Merged
Conversation
…a dead proxy - Output JSON/CSV/sidecar are written atomically (temp file, fsync, rename), new files keep the umask's mode and existing files keep their own. - CSV cells starting with = + - @ TAB CR LF are neutralised (text only, CSV only); csv_cells_escaped is recorded in the sidecar under the caller's extra. - Every engine checks a per-page SolveBudget before buying a solve; retries and rotations no longer buy another one. - Playwright and pyppeteer pass goto()'s HTTP status to the classifier. - reCAPTCHA: a challenge frame means v2; an Enterprise widget is not solved with an ordinary task. - --pages/--concurrency/--proxy-block-retries/--delay/--retry-delay reject nonsense values with a usage error in all engines; the Scraper API client rejects a negative --retries/--retry-delay. - Credential scan judges exclusions relative to the repo, fails on 0 files, and reads .json and renamed .env copies. .gitignore covers more dumps. - --allow-empty complete runs are status complete. - Canary fails when MEDIAMARKT_PROXY is missing, checks the proxy first and names why it cannot deliver a page, and probes the site from the bare runner. - Private-note citations removed; a suite check keeps them out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jehrr
marked this pull request as ready for review
October 15, 2026 21:16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes from the 2026-10-14 audit, plus a canary that says why it is red. The base is
05824fe, which is the audited SHA.The canary (live issue)
MEDIAMARKT_PROXYwas set. Every scheduled run since 2026-09-10 shows the same thing in the job log:Using proxy exit ***eu.proxy.2captcha.com:2334, thenTimeout loading … (attempt 1/3),2/3,Gave up. That surfaced as exit 4 in September and as exit 5 since then, and exit 5 is printed as "Unexpected exit code". The 09-09 run used the same proxy host and fetched 3 pages and 36 rows in about 17 s. A timeout with no 403 and no proxy error, starting the day after it last worked, is what an expired or out-of-traffic proxy account looks like. The secret most likely needs replacing.::error::Missing secret MEDIAMARKT_PROXY. It no longer skips green..github/canary_preflight.py) asks the proxy directly, with short timeouts, before the browser run. It names the cause: gateway unreachable, credentials rejected (407), tunnel refused, or a connection that delivers nothing. It also warns when the login asks for a region other thande. The secret is never printed, only host:port and the region code.::error::, and the password never appears in the output.5)branch of the Interpret step is covered by Canary knows exit 5, refusal names the country, dumps are 0600 #19 and was not touched here.Fixes
_atomic: a temp file in the target's directory, fsync,os.replace, and the temp file removed on error. A new file gets0o666 & ~umask; an existing file keeps its mode.csv_cells_escapedis merged under the caller'sextra.page_flow.SolveBudget, withSOLVES_PER_PAGE = 1, is created once per page before the block-retry loop. It is checked beforesolve_recaptchain all three engines and is not reset on rotation.goto()'s status toclassify, and clear it after a solve. Selenium passes an explicitNone, because it has no status, and says so in the code.size=invisible. An Enterprise widget is flagged, andsolve_recaptcharaises before any request.--pages/--concurrencymust be ≥1, and--proxy-block-retries/--delay/--retry-delaymust be ≥0, in all engines (exit 2, naming the flag). Inscraper_api_client,--retries/--retry-delaymust be ≥0..jsonfiles and.env.*backups (but not.env) are scanned..gitignore: adds*.page[0-9]*,/*.html,live/out/output/dumps/scratch/.--allow-emptyon a complete run with 0 rows now givesstatus: complete.--wait-elementexample uses this site's selector.test_no_private_citations, uses assembled tokens. The v0.1.8 release notes were edited in the same way.Evidence
goto()givesblockedeven when the body holds a product link.ci_checks.py --allpasses.PYTHONDONTWRITEBYTECODE=1and__pycache__cleared. Each went red on the predicted named check, and the restored tree is green. They cover:take()off-by-one;--pagesback toint, the delay validator, a re-added citation.venv/,.venv/andbuild/out/it scans 36 files. A planted key in.env.bakordump.jsonis caught. The old absolute-path line undervenv/→ "0 files scanned" FAILED.--pages 0and--delay -1→ argparse error naming the flag in pyppeteer and Selenium.Not changed
Covered by Canary knows exit 5, refusal names the country, dumps are 0600 #19 (owner, open):
5)branch and README exit-5 row;Canary knows exit 5, refusal names the country, dumps are 0600 #19 conflicts with main on CHANGELOG already, and will also conflict with this PR in CHANGELOG.
Owner decisions or out of scope:
--concurrencyin pyppeteer/Selenium, and flag parity;should_solve/counts_as_blocked;No paid calls, no proxies, no credentials used.
🤖 Generated with Claude Code