Skip to content
This repository was archived by the owner on Oct 22, 2026. It is now read-only.

feat(scripts): let CI configure the smoke loop entirely through env - #328

Merged
PetrefiedThunder merged 2 commits into
mainfrom
claude/site-seo-optimization-p7drdl
Aug 20, 2026
Merged

PetrefiedThunder merged 2 commits into
mainfrom
claude/site-seo-optimization-p7drdl

Conversation

@PetrefiedThunder

@PetrefiedThunder PetrefiedThunder commented Aug 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

--tool and --other-tool already fall back to $CI_SMOKE_TOOL and $CI_SMOKE_OTHER_TOOL. --tool-args had no equivalent, so a CI job could satisfy every part of the off-loopback guard except the payload, and had to reach for argv for the one remaining piece — the wrong place for a secret-adjacent value, and enough to make the guard read as an obstacle rather than a check.

This is not cosmetic. #327 wires the loop into CI, and its production step (ci.yml) invokes it with neither flag. The guard I added in #326 exits 2 before any network call, so that step fails on every push to main:

$ API_URL=https://api.thisisatest.tech CI_SMOKE_AGENT_KEY=… python scripts/constant_test_loop.py
[constant-test] configuration error: refusing to auto-select a tool against
https://api.thisisatest.tech. Pass --tool (or set $CI_SMOKE_TOOL) …
EXIT=2

Configuring that step through secrets is only possible once the payload has an environment path. That break is mine — #326 tightened the interface after #327 was written against the old one.

Behavior

  • --tool-args falls back to $CI_SMOKE_TOOL_ARGS; an explicit flag still wins.
  • An explicitly empty payload set through the environment counts as a decision, exactly as --tool-args '{}' does, so it satisfies the guard rather than falling through to "unset".
  • Malformed values are ConfigurationError (exit 2), never SmokeTestFailure (exit 1) — invalid JSON, or valid JSON that isn't an object, means the loop was configured wrong, not that the trust plane broke. The message names which source was bad, since the flag and the variable fail identically otherwise.
  • A list or scalar is rejected up front rather than forwarded as the tool's arguments and refused downstream, where it would surface as a tool error and read as the product failing.

Dropping argparse's type=json.loads is part of that: it exits 2 for bad JSON too, but with usage text that never says what was wrong with the payload.

Type of Change

  • feat
  • fix
  • docs
  • refactor
  • test
  • chore

Validation

  • pytest -q — 19 loop tests (15 pre-existing, unchanged) plus the 46 site tests; ruff check . clean
  • python scripts/check_doc_references.py — 309 references across 246 files resolve
  • python scripts/export_openapi.py --check — not applicable, no API surface changed
  • python scripts/generate_sim_inventory.py --check — not applicable
  • manually tested key endpoint paths — against production with a placeholder credential: with only CI_SMOKE_TOOL and CI_SMOKE_TOOL_ARGS set, the run reaches real authentication (403 on the placeholder) rather than either guard; a malformed value reports $CI_SMOKE_TOOL_ARGS is not valid JSON at exit 2
  • updated docs/config examples if needed — docs/constant-test-loop.md gains a flag/variable table and states the exit-code contract for a malformed payload
  • no new claim contradicts SECURITY_LIMITATIONS.md — no trust-plane behavior changes; this is argument resolution in a client script

New tests cover flag-beats-env precedence, the env fallback, an explicitly empty payload counting as a decision, malformed JSON and non-object JSON as configuration errors with the source named, and an end-to-end run configured purely through environment getting past both guards.

Risk

  • low
  • medium
  • high

Additive: a new fallback where there was none, so existing invocations behave identically. The one behavior change is that malformed --tool-args now reports what was wrong instead of argparse usage text — same exit code, better message. Rollback is a revert.

Follow-up for #327

With this merged, #327's production step can be configured entirely through repository secrets:

env:
  API_URL: https://api.thisisatest.tech
  CI_SMOKE_AGENT_KEY: ${{ secrets.CI_SMOKE_AGENT_KEY }}
  CI_SMOKE_TOOL: partner.echo
  CI_SMOKE_TOOL_ARGS: '{"text": "ci constant test loop"}'
run: python scripts/constant_test_loop.py

Worth confirming partner.echo is actually registered on production before relying on it — I've only ever exercised this loop against a local instance.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ri576RW3ocgqpx1xYN9vBm


Generated by Claude Code


Note

Cursor Bugbot is generating a summary for commit 20dc3eb. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 20, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in: 27 minutes

Limit details: You’ve used the included review currently available. Your 92 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dfc9e6aa-0bc9-40eb-b18e-3e713409ee32

📥 Commits

Reviewing files that changed from the base of the PR and between 6cbdc3d and 31064f0.

📒 Files selected for processing (3)
  • docs/constant-test-loop.md
  • scripts/constant_test_loop.py
  • tests/test_constant_test_loop.py

Comment @coderabbitai help to get the list of available commands.

@vercel

vercel Bot commented Aug 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-middleware-web Ready Ready Preview Aug 20, 2026 11:00am

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 20dc3eb. Configure here.

Comment thread tests/test_constant_test_loop.py
Comment thread tests/test_constant_test_loop.py
PetrefiedThunder pushed a commit that referenced this pull request Aug 20, 2026
The step ran on every push to main and would have failed on all of them,
two ways over.

With CI_SMOKE_AGENT_KEY unset — the state today — the loop does not skip.
It falls through to self-provisioning, and /v1/dev-keys/self-provision is
disabled on production by design, so the run fails. The PR described this
path as skipping gracefully; there was no skip to do it. A secret cannot be
tested in a step-level `if`, so the check has to live in the script.

With the key set it would still have failed: #326 made --tool and
--tool-args mandatory off loopback, and the step passed neither, so the loop
exits 2 before opening a connection. Which tool a monitor invokes on every
push, and with what payload, is an operator's decision rather than one
discovery makes for them.

The step now skips unless both the credential and CI_SMOKE_TOOL are
configured, and passes --tool and --tool-args explicitly when they are.
Enabling the production smoke is deliberate in two parts, which is what the
description already promised.

Passing the flags rather than relying on environment fallbacks keeps this
independent of #328: it works against main as it stands today.

Verified by running the step's own shell: unset skips at exit 0, key-without-
tool skips at exit 0, and fully configured reaches the loop rather than
either guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ri576RW3ocgqpx1xYN9vBm
claude added 2 commits August 20, 2026 11:00
--tool and --other-tool already fall back to $CI_SMOKE_TOOL and
$CI_SMOKE_OTHER_TOOL, but --tool-args had no equivalent. A CI job could
therefore satisfy every part of the off-loopback guard except the payload,
and had to reach for argv to supply the one remaining piece — which for a
secret-adjacent value is the wrong place, and which makes the guard read as
an obstacle rather than a check.

--tool-args now falls back to $CI_SMOKE_TOOL_ARGS. This is not cosmetic: PR
327 wires the loop into CI and its production step invokes it with neither
flag, so the guard added in #326 exits 2 on every push to main. Configuring
that step through secrets is only possible once the payload has an
environment path.

An explicitly empty payload set through the environment counts as a
decision, exactly as --tool-args '{}' does on the command line, so it
satisfies the guard rather than falling through to "unset".

Malformed values are ConfigurationError (exit 2), never SmokeTestFailure:
invalid JSON, or valid JSON that is not an object, means the loop was
configured wrong rather than that the trust plane broke. The message names
which source was bad, since --tool-args and $CI_SMOKE_TOOL_ARGS fail
identically otherwise. A list or scalar is rejected up front rather than
forwarded as the tool's arguments and refused downstream, where it would
surface as a tool error and read as the product failing.

Dropping argparse's type=json.loads is part of that: it exits 2 for bad
JSON too, but with usage text that never says what was wrong with the
payload.

Verified against production with a placeholder credential: with only
CI_SMOKE_TOOL and CI_SMOKE_TOOL_ARGS set, the run reaches real
authentication instead of either guard, and a malformed value reports
"$CI_SMOKE_TOOL_ARGS is not valid JSON" at exit 2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ri576RW3ocgqpx1xYN9vBm
…MOKE

test_environment_alone_satisfies_the_off_loopback_guard pointed at
api.thisisatest.tech, so every pytest run — including pull-request CI —
opened a real HTTPS session to production, and a slow reject would surface
as TimeoutExpired rather than a configuration assertion. That is precisely
the unattended production traffic the guard under test exists to make
deliberate. It now targets a reserved .invalid host (RFC 6761): still
non-loopback, so both guards apply, but it never resolves.

test_tool_args_resolve_from_flag_environment_or_neither asserted that
_resolve_tool_args(None) returns None without clearing CI_SMOKE_TOOL_ARGS.
The resolver reads the real environment, so an ambient value made the
assertion fail even though the flag path was correct.

Running the suite under a deliberately hostile environment to check that
second fix surfaced a third problem, which the new fallback made reachable:
test_constant_test_loop_never_logs_api_key passes **os.environ through to
the subprocess, so an exported CI_SMOKE_TOOL_ARGS is forwarded as the tool's
arguments and the run dies with "_write_note() got an unexpected keyword
argument 'ambient'" — a message that points at the tool rather than at the
stray variable. The fragility predates this branch (an ambient
CI_SMOKE_WALLET_ID would mislead the same way) but only became reachable
once the payload gained an environment path. That test now strips CI_SMOKE_*
before setting the three values it means to set, matching what the other
subprocess tests here already do.

Verified with CI_SMOKE_TOOL_ARGS, CI_SMOKE_TOOL and CI_SMOKE_WALLET_ID all
exported to junk: 19 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ri576RW3ocgqpx1xYN9vBm
@PetrefiedThunder
PetrefiedThunder force-pushed the claude/site-seo-optimization-p7drdl branch from 1b64b50 to 31064f0 Compare August 20, 2026 11:00
@PetrefiedThunder
PetrefiedThunder marked this pull request as ready for review August 20, 2026 11:16
@PetrefiedThunder
PetrefiedThunder merged commit 254a4ad into main Aug 20, 2026
19 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31064f07e2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +606 to +609
try:
parsed = json.loads(raw)
except json.JSONDecodeError as exc:
raise ConfigurationError(f"{source} is not valid JSON: {exc}") from exc

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-finite constants in tool arguments

When CI_SMOKE_TOOL_ARGS contains NaN, Infinity, or -Infinity, Python's json.loads accepts these non-standard constants, so the value passes this validation as a dictionary. Once the configured tool is invoked, HTTPX refuses to serialize the payload with an uncaught ValueError, producing a traceback and exit 1 instead of the documented configuration error and exit 2. Configure json.loads with a rejecting parse_constant callback and cover these malformed-input cases.

AGENTS.md reference: AGENTS.md:L83-L101

Useful? React with 👍 / 👎.

This branch was successfully deployed

1 active deployment
Preview — 31064f07 Deployed Aug 20, 2026 by vercel[bot]
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants