Skip to content

feat(account): claim links expire after 30 days and accounts get claimed in the browser, so refresh the stored link and stop offering it once claimed - #65

Merged
AnderRV merged 5 commits into
mainfrom
feat/claim-link-refresh
Oct 18, 2026

Conversation

@AnderRV

@AnderRV AnderRV commented Oct 18, 2026

Copy link
Copy Markdown
Member

Why

Claim links expire 30 days after they are issued, and the account is now claimed in the browser by proving an email (Google, GitHub or a 6-digit code). The CLI kept showing the link stored at signup, so after 30 days zenrows account claim and the quota-error hint pointed at a dead page, and both kept offering the link after the account was claimed.

Client attribution was also too broad: any exported OPENAI_API_KEY labelled the shell as Codex, and any CLAUDE_* variable as Claude Code.

What

  • zenrows account claim. Asks the account endpoint whether the account is already claimed, and says so if it is. Otherwise it requests a fresh link (POST /api/agent/claim) when the stored one is missing, untrusted or 25+ days old, and saves it with its issue time.
  • Quota/402 hints. Same logic, checked at most every 6 hours, with 5-second timeouts. If the backend can't be reached, the stored link is kept only while it is under 30 days old; otherwise the error says to run zenrows account claim.
  • Parallel commands. A lock file with an owner token ensures only one process talks to the claim endpoints at a time; a stale lock is taken over safely. The 6-hour slot is claimed before any network call, and the stored state is re-read before minting and before writing. In a test, eight parallel zenrows fetch processes hitting a 402 made exactly one claim request.
  • The key only goes to Zenrows. Claim and account requests are sent only to https zenrows.com hosts (no userinfo), or to loopback for local testing. A signup URL from the environment, a project config, or a discovery document that fails this check falls back to the built-in endpoint, so a cloned project's config can't redirect the key.
  • Files. secrets.json and account.json are created 0600 from the start (temp file plus rename), in a 0700 directory.
  • Other fixes. A 409 (already_claimed or not_agent_account) is treated as final. A non-JSON 200 gets an accurate error. Claim links with userinfo are rejected.
  • Client detection. openai-codex only for CODEX_SANDBOX, CODEX_SANDBOX_NETWORK_DISABLED or CODEX_THREAD_ID. claude-code only for CLAUDECODE, CLAUDE_CODE_ENTRYPOINT or CLAUDE_CODE_CHILD_SESSION. Anything else is unknown.
  • Both SKILL.md copies and the README describe the current claim flow.

Verification

  • npm run typecheck clean. npm test: 409 pass, 0 fail.
  • New tests cover refresh thresholds, the throttle, claimed detection, network fallbacks, the lock (including a live lock held by another process), override validation, discovery validation, the 409s, a non-JSON 200, file permissions and detection. Each behaviour was broken once to confirm its test fails.
  • End to end, the built CLI ran against a local stub with an isolated home directory. It covered: signup writing 0600 files; claim at under 25 days (no request) and at 26 days (refresh); eight parallel 402s (one claim request, no leftover lock); backend down with a 31-day link; and a claimed account (hints stop, "already claimed").

…ex, so attribute Codex only from its own CODEX_* variables
…med in the browser, so refresh the stored link and stop offering it once claimed

`zenrows account claim` asks for a fresh link when the stored one is missing
or 25+ days old, and reports an already-claimed account. Quota errors check
claim status at most every 6 hours, keep a stored link only while it is under
30 days old, point to `zenrows account claim` otherwise, and accept only
https zenrows.com claim URLs.
…ent is installed, so attribute Claude Code and Codex from the variables they set when running a command
…and an untrusted discovery document could redirect the API key

Only one process refreshes the claim link at a time (an exclusive lock file
next to account.json), the 6-hour slot is taken before any network call, and
account.json is re-read before minting so a link another process just stored
is reused. A discovered signup endpoint is used only when it is https on
zenrows.com, and the account/claim URLs fall back to the built-in endpoint
when the signup URL does not end in /signup. A 409 not_agent_account is final
like already_claimed, a non-JSON 200 is reported as such, claim URLs with
userinfo are rejected, and secrets.json/account.json are written 0600 from
creation (temp file + rename) in a 0700 directory.
… config could receive the API key on the claim and status checks

The account and claim endpoints are derived from the signup URL only when it
is https on zenrows.com or http(s) on loopback, with no userinfo; anything
else uses the built-in endpoint. Both requests now resolve against the
caller's project and run URL discovery through the same 5-second fetch. The
claim lock holds a random token: release deletes it only while the token is
still ours, and a stale lock is renamed aside before takeover and put back
if it turns out to be live. saveApiKey no longer chmods a file that is
already written 0600.
@AnderRV AnderRV self-assigned this Oct 18, 2026
@AnderRV
AnderRV merged commit ded5602 into main Oct 18, 2026
3 checks passed
@AnderRV
AnderRV deleted the feat/claim-link-refresh branch October 18, 2026 12:05
@github-actions github-actions Bot mentioned this pull request Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant