fingerprint_client: read the key from .env, like everything else does - #27
Conversation
`--key` defaulted to `os.environ.get("TWOCAPTCHA_KEY")` alone, so a key put
in `.env` — exactly as §3, the README and .env.example instruct — worked for
every engine and failed HERE with "No API key". A documented mechanism not
applied on one path, which is the shape of half the defects §16 lists. Found
on a sibling repo's first live --fingerprint run, then checked across the
family before patching: five repos had it, one had already fixed it.
The fix reads through `env_config.env_value` rather than `os.environ.get`,
and that choice is measured rather than stylistic. With
TWOCAPTCHA_KEY=your_2captcha_api_key_here exported:
os.environ.get sends the placeholder to the API; the run reports
"Fingerprint API rejected the key (401) — note this is a
separate subscription", sending the reader off to check a
subscription they never needed
env_value "TWOCAPTCHA_KEY is still set to the placeholder from
.env.example — treating it as unset"
`load_env()` is called here rather than relied upon, because this is a
standalone entry point that no engine has necessarily run first.
Pinned by a check that was verified to FAIL on the old code: that it loads
.env itself, that it reads through the loader, that it does NOT read
os.environ directly, that a placeholder still reads as unset, and that the
help string does not interpolate the default — which is one substring away
from printing a live credential to anyone who types --help.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| # filled the key in. `env_value` says so instead, by name. | ||
| env_config.load_env() | ||
| p.add_argument("--key", default=env_config.env_value("TWOCAPTCHA_KEY"), | ||
| help="API key. Defaults to TWOCAPTCHA_KEY from the " | ||
| "environment or .env (safer than argv).") | ||
| # Measured against the live API on 2026-09-09, because the example this |
There was a problem hiding this comment.
env_config.env_value("TWOCAPTCHA_KEY") is passed directly as default=, so it's evaluated eagerly the instant add_argument() runs — before parse_args() ever sees argv. Since env_value() (env_config.py#L144-L154) logs a warning whenever the value matches a .env.example placeholder, this means:
- A bare
--helpinvocation logs the placeholder warning even though no key resolution is actually happening. - A user who has the placeholder in
.envbut correctly passes--key sk_real...on the command line still gets the spurious "is still set to the placeholder... treating it as unset" warning, even though their explicit flag overrides the default and the placeholder is never actually used.
The sibling scraper_api_client.py avoids this exact problem: it keeps default=os.environ.get("TWOCAPTCHA_KEY") (side-effect-free) and instead calls env_config.apply(args, keys={"TWOCAPTCHA_KEY": "key", ...}) after parse_args() (scraper_api_client.py#L294-L298) — apply() only calls env_value() if the destination is still falsy (env_config.py#L166-L173), so an explicit --key short-circuits before the placeholder check ever runs.
Suggest following that same shape here: keep the default as a plain env read and defer the env_value()/placeholder check to after parse_args(), only applying it if args.key is still unset.
--keydefaulted toos.environ.get("TWOCAPTCHA_KEY")alone, so a key putin
.env— exactly as §3, the README and.env.exampleinstruct — workedfor every engine and failed here with "No API key". A documented
mechanism not applied on one path, which is the shape of half the defects
§16 lists.
Found on a sibling repo's first live
--fingerprintrun, then checkedacross the family before patching, per §16: five repos had it, one
(catawiki) had already fixed it. This is that fix, made identical everywhere.
Why
env_valueand notos.environ.getMeasured both ways with
TWOCAPTCHA_KEY=your_2captcha_api_key_hereexported:os.environ.getFingerprint API rejected the key (401) — note this is a separate subscription from captcha solvingenv_valueTWOCAPTCHA_KEY is still set to the placeholder from .env.example — treating it as unsetThe first sends the reader off to check a subscription they never needed.
load_env()is called here rather than relied upon, because this is astandalone entry point that no engine has necessarily run first.
Pinned
A new check, verified to fail on the old code by reverting: that it loads
.envitself, that it reads through the loader, that it does not reados.environdirectly, that a placeholder still reads as unset, and that thehelp string does not interpolate its default — which is one substring away
from printing a live credential to anyone who types
--help.Offline suite green,
--helpworks,ci_checks.py --allpasses.🤖 Generated with Claude Code