fix(780): address cold review — narrow S105, guard the population, fix the doc claims
Cold review (Opus, isolated worktree) cleared the mechanical reformat: 25 of 33 files AST-identical, the suite's collected node IDs identical to main, and the three percent-format -> f-string conversions byte-equivalent at both call sites. Everything below is config/CI/doc layer. M4: `S105` is no longer exempted directory-wide. A blanket over `scripts/tests/**` gives up hardcoded-credential coverage over the largest Python surface in the repo — permanently, and this is the only Python lint we run — to suppress 8 known lines. Those 8 now carry a per-site `# noqa: S105`; removing one is red (witnessed). Only `S101` stays directory-wide. M1: `ruff check .` on an empty population prints a stderr warning and exits 0, so a stray `exclude` would switch the gate off silently — the vacuity failure `testing.guard-derives-population-from-source` is about. The step now compares `--show-files` against `git ls-files '*.py'` and fails on drift. Mutation proof: a top-level `exclude = ["scripts"]` takes `ruff check` to green and the step to `::error::… 0 Python files but git tracks 33`. Note the same exclude under `[format]` does NOT reproduce it — it scopes to the formatter, and a mutation written that way passes while proving nothing. M3: lint moved ahead of the preflights. Behind `Preflight jq version` (a hard `--expect` tripwire) the lint would go dark for as long as the jq contract is broken, under a red that names jq. H3: ruff.toml named a `Script lint (ruff)` job that does not exist — wrong on the day it shipped. H1: the "72 errors" claim is now reproducible against the committed config (it was only true of the pre-M4 config), carries its reproduction command and base sha, and splits honestly: 54 fixed in code, 18 per-site `# noqa`s. H2: `docs/ci-cd.md` still said the suite is ~190 tests / ~10s next to a new "four-minute" figure. Measured: 773 tests, ~4.5 min. The no-path-filter rationale rested on the dead number; it now rests on the input set, which is the real reason. M2: the nine E501 `# noqa`s claimed splitting "would change the payload". Implicit string concatenation preserves the bytes exactly, so the stated reason was false nine times. They say what is actually true: kept one-line for greppability. Nits: the record no longer claims the config is identical to the operator's global one (per-file-ignores differ), and the unpinned pytest/pyyaml asymmetry is stated rather than left looking like an oversight. Refs: #780 Decisions-Edit: yes
This commit is contained in:
@@ -347,6 +347,40 @@ jobs:
|
||||
# all of scripts/ rather than by reading the files that seemed relevant: the first cut of this
|
||||
# job claimed "pure stdlib", passed locally on a machine that happened to have PyYAML, and
|
||||
# went red in CI on a collection error.
|
||||
# ersatztv#780. Lint runs FIRST — before the pip installs, the preflights and the ~4-minute
|
||||
# pytest run — so a style red arrives in seconds and, more importantly, so it cannot be
|
||||
# skipped by an unrelated earlier step. Sitting after `Preflight jq version` (an `--expect`
|
||||
# tripwire) would take the lint dark for as long as the jq contract is broken, under a red
|
||||
# that says "jq".
|
||||
#
|
||||
# The version is PINNED: an unpinned ruff makes the verdict a function of whenever the job ran
|
||||
# — the same environment-divergence the committed ruff.toml exists to close. Bumping it is a
|
||||
# deliberate PR (new rules may fire), exactly like the jq pin below. `pytest`/`pyyaml` are
|
||||
# deliberately NOT pinned: a pytest release does not add assertions to your suite, a ruff
|
||||
# release adds rules to your lint.
|
||||
- name: Install ruff
|
||||
run: python3 -m pip install --disable-pip-version-check --quiet 'ruff==0.12.11'
|
||||
# Population + anti-vacuity. `ruff check .` discovers every Python file ruff can see from the
|
||||
# repo root, so a new .py anywhere is covered without this job enumerating anything — but an
|
||||
# EMPTY population is a GREEN `ruff check`: it prints "warning: No Python files found" to
|
||||
# stderr and exits 0. A stray `exclude` in ruff.toml, a wrong working directory, or a future
|
||||
# change to ruff's default excludes would therefore switch this gate off silently. So compare
|
||||
# the discovered set SIZE against git's tracked .py set first and fail loudly on drift in
|
||||
# either direction (docs/decisions/records/testing/guard-derives-population-from-source.md).
|
||||
- name: Lint scripts (ruff check)
|
||||
run: |
|
||||
seen=$(python3 -m ruff check . --show-files | wc -l | tr -d ' ')
|
||||
tracked=$(git ls-files '*.py' | wc -l | tr -d ' ')
|
||||
if [ "$seen" -ne "$tracked" ]; then
|
||||
echo "::error::ruff would check $seen Python files but git tracks $tracked." \
|
||||
"Either a tracked .py is excluded (dead lint) or ruff sees untracked files." \
|
||||
"Reconcile before trusting this gate — see ersatztv#780."
|
||||
exit 1
|
||||
fi
|
||||
echo "Population OK: $seen tracked Python files"
|
||||
python3 -m ruff check .
|
||||
- name: Lint scripts (ruff format --check)
|
||||
run: python3 -m ruff format --check .
|
||||
- name: Install test dependencies
|
||||
run: python3 -m pip install --disable-pip-version-check --quiet pytest pyyaml
|
||||
# Preflight, not an install (ersatztv#390 removed run-time `apt-get` from CI on purpose).
|
||||
@@ -375,19 +409,5 @@ jobs:
|
||||
# See docs/ci-cd.md -> "The jq contract".
|
||||
- name: Preflight jq version
|
||||
run: ./scripts/jq-preflight.sh --expect 1.6
|
||||
# ersatztv#780. Lint runs BEFORE the 4-minute pytest run so a style red arrives in seconds.
|
||||
# The version is PINNED: an unpinned ruff makes the verdict a function of whenever the job ran
|
||||
# — the same environment-divergence the committed ruff.toml exists to close. Bumping it is a
|
||||
# deliberate PR (new rules may fire), exactly like the jq pin above.
|
||||
#
|
||||
# Population: `ruff check .` / `ruff format --check .` discover every Python file ruff can see
|
||||
# from the repo root, so a new .py anywhere is covered without this job enumerating anything
|
||||
# (docs/decisions/records/testing/guard-derives-population-from-source.md).
|
||||
- name: Install ruff
|
||||
run: python3 -m pip install --disable-pip-version-check --quiet 'ruff==0.12.11'
|
||||
- name: Lint scripts (ruff check)
|
||||
run: python3 -m ruff check .
|
||||
- name: Lint scripts (ruff format --check)
|
||||
run: python3 -m ruff format --check .
|
||||
- name: Run scripts/tests
|
||||
run: PYTHONPATH=. python3 -m pytest scripts/tests -q
|
||||
|
||||
+23
-11
@@ -911,7 +911,7 @@ was witnessed, never that a run was cancelled or broke.
|
||||
> `review-verdict/h10`). Promoting it to required is a branch-protection change, tracked separately.
|
||||
|
||||
Runs the repository's Python test suite: `PYTHONPATH=. python3 -m pytest scripts/tests -q`
|
||||
(~190 tests at time of writing, ~10s; the suite grows, so treat the figure as indicative). It covers the decision-corpus parser/validator/catalog builder, the ersatztv#610
|
||||
(773 tests at `706674272`, ~4.5 min; the suite grows fast — it was ~190 tests / ~10s when this job landed in #631 — so treat the figure as a dated snapshot, not a budget). It covers the decision-corpus parser/validator/catalog builder, the ersatztv#610
|
||||
migration-equivalence harness, the merge-consent exemption logic and the ersatztv#622 review-verdict
|
||||
poster.
|
||||
|
||||
@@ -932,17 +932,29 @@ name keeps a real failure unambiguous.
|
||||
input set spans more than one directory — `test_post_review_verdict.py` and
|
||||
`test_merge_consent_exemption.py` execute the real `scripts/post-review-verdict.sh` and
|
||||
`.claude/hooks/pretooluse-merge-consent.sh` — so a `scripts/**` filter would silently miss a
|
||||
`.claude/hooks/**` edit. At ~10s, a filter buys nothing but drift.
|
||||
`.claude/hooks/**` edit. The reason is the input set, not the cost: the suite was ~10s when that was
|
||||
decided and is now ~4.5 min, and it would still be wrong to filter on `scripts/**`.
|
||||
|
||||
**It also lints (ersatztv#780).** Before the pytest step it installs a **pinned** `ruff==0.12.11`
|
||||
and runs `ruff check .` and `ruff format --check .` against the repo-root `ruff.toml`. Lint runs
|
||||
first so a style red arrives in seconds rather than after the four-minute suite. Two things are
|
||||
deliberate: the config is *committed* (without it ruff falls back to whatever
|
||||
`~/.config/ruff/ruff.toml` the operator's machine has, so a second machine lints this repo
|
||||
differently or not at all), and the version is *pinned* (an unpinned install makes the verdict a
|
||||
function of when the job ran — the same divergence one layer up, and the same argument as the `jq`
|
||||
pin above). Bumping either is a deliberate PR, because a new ruff release adds rules. `pyright` is
|
||||
not gated; the reasoning is in `ci.python-lint-ruff-config-committed`.
|
||||
**It also lints (ersatztv#780).** Its **first** steps install a **pinned** `ruff==0.12.11` and run
|
||||
`ruff check .` and `ruff format --check .` against the repo-root `ruff.toml`. Four things are
|
||||
deliberate:
|
||||
|
||||
- The config is **committed**. Without it ruff falls back to whatever `~/.config/ruff/ruff.toml` the
|
||||
operator's machine has, so a second machine lints this repo differently or not at all.
|
||||
- The version is **pinned** — an unpinned install makes the verdict a function of when the job ran,
|
||||
the same divergence one layer up, and the same argument as the `jq` pin below. `pytest`/`pyyaml`
|
||||
stay unpinned on purpose: a pytest release does not add assertions to your suite, a ruff release
|
||||
adds rules to your lint.
|
||||
- Lint runs **before** the preflights, not after. `Preflight jq version` is a hard `--expect`
|
||||
tripwire; a lint sitting behind it goes dark for as long as the jq contract is broken, under a red
|
||||
that says "jq".
|
||||
- `ruff check` is preceded by a **population check**: it compares the file count ruff would check
|
||||
(`--show-files`) against `git ls-files '*.py'` and fails on any drift. An empty population is a
|
||||
*green* `ruff check` — it warns on stderr and exits 0 — so without this, a stray `exclude` in
|
||||
`ruff.toml` switches the whole gate off silently.
|
||||
|
||||
`pyright` is not gated; the reasoning and the exemption list are in
|
||||
`ci.python-lint-ruff-config-committed`.
|
||||
|
||||
**Dependencies: `pytest` and `pyyaml`** — the complete third-party set across `scripts/`, established
|
||||
by an AST import scan rather than by reading the files that looked relevant. PyYAML does **not**
|
||||
|
||||
@@ -18,15 +18,31 @@ shell gate whose behaviour was a function of an untested interpreter version) an
|
||||
passed on a fast laptop and flaked on a starved CI VM): the verdict was a property of the environment
|
||||
rather than of the repo.
|
||||
|
||||
The committed config is the operator's global one, pinned here — that is what the tree was de-facto
|
||||
written against, so adopting it cost a mechanical reformat rather than a rewrite.
|
||||
The committed config is the operator's global one apart from `per-file-ignores`, which is narrowed
|
||||
to `scripts/tests/**`. That is what the tree was de-facto written against, so adopting it cost a
|
||||
mechanical reformat rather than a rewrite: 72 findings against `706674272`, of which 54 were fixed in
|
||||
code (mostly by the format pass) and 18 carry a per-site `# noqa` with its reason inline.
|
||||
|
||||
**Two exemptions are in the config, not in people's heads.** `scripts/tests/**` is exempt from `S101`
|
||||
(the suite asserts) and from `S105`: those tests hand the real hooks deliberately fake credentials
|
||||
(`env["ETV_GITEA_TOKEN"] = "stub"`), which fires the hardcoded-password rule eight times with no true
|
||||
positive anywhere in the tree. Everything else that fired was fixed, not ignored. A per-site
|
||||
`# noqa` carries its reason inline — the two that survive are a percent-format template whose payload
|
||||
contains literal braces, and one-line JSON fixtures that cannot be split without changing the payload.
|
||||
**One exemption is directory-wide, and it is the boring one.** `S101` for `scripts/tests/**`, because
|
||||
a test suite asserts. **`S105` is deliberately NOT directory-wide**, even though all eight of its
|
||||
hits are stub credentials handed to the real hooks (`env["ETV_GITEA_TOKEN"] = "stub"`) with no true
|
||||
positive in the tree today. A directory blanket would give up hardcoded-credential coverage over the
|
||||
largest Python surface in the repo, permanently, to suppress eight known lines — and this is the only
|
||||
Python lint the repo runs, so nothing else would catch a real token pasted into a fixture next year.
|
||||
Eight per-site `# noqa: S105` cost the same and keep the rule live.
|
||||
|
||||
**The population is checked, because an empty one is green.** `ruff check .` on a tree with no Python
|
||||
files prints `warning: No Python files found` to stderr and exits **0**, so a stray `exclude`, a wrong
|
||||
working directory, or a change to ruff's default excludes would switch this gate off without a red.
|
||||
The CI step therefore compares `ruff check . --show-files | wc -l` against `git ls-files '*.py' | wc -l`
|
||||
and fails on drift in either direction. Witnessed, not assumed: adding `exclude = ["scripts"]` takes
|
||||
`ruff check .` to green-with-a-warning and the step to `::error::ruff would check 0 Python files but
|
||||
git tracks 33`. Construct that mutation at the TOP level of `ruff.toml`: an `exclude` under the
|
||||
`[format]` table scopes to the formatter only, so a mutation written there passes and proves nothing.
|
||||
|
||||
**Lint runs first in the job, ahead of the preflights.** `Preflight jq version` is a hard `--expect`
|
||||
tripwire; a lint step behind it stops running for as long as the jq contract is broken, under a red
|
||||
that names jq. Ordering is the difference between a gate that is skipped and one that is not.
|
||||
|
||||
**`pyright` is deliberately NOT gated.** Its only findings here are `reportMissingImports` for
|
||||
`etv_client` in `scripts/scripted-schedules/entrypoint.py`, resolvable only inside that script's
|
||||
@@ -37,4 +53,6 @@ a judgement that type checking does not belong.
|
||||
**The pin is the second half of the fix.** An unpinned `pip install ruff` re-introduces exactly the
|
||||
divergence the config closes, one layer up: the verdict becomes a function of *when* the job ran. Same
|
||||
argument as the `jq` pin in the same job (`ci.jq-version-contract`), and the same consequence — a bump
|
||||
is a PR someone reads.
|
||||
is a PR someone reads. `pytest` and `pyyaml` in the same job stay unpinned, and the
|
||||
asymmetry is the point rather than an oversight: a pytest release does not add assertions to your
|
||||
suite, a ruff release adds rules to your lint.
|
||||
|
||||
@@ -446,13 +446,25 @@ committed ruff config and enforces it, or the global instruction stops claiming
|
||||
something it does not.
|
||||
|
||||
**Resolved 2026-08-21 (#780): the repo moved.** `ruff.toml` is committed at the root and the
|
||||
`script-tests` job runs `ruff check .` + `ruff format --check .` against a pinned `ruff==0.12.11`
|
||||
before pytest. Measured against the committed config on the day of the fix — the tree had grown since
|
||||
the 2026-08-13 row above — it was **72 errors and 20 files needing reformatting**; all were fixed, with
|
||||
exactly two categories exempted in the config rather than in anyone's head (`S101` and `S105` under
|
||||
`scripts/tests/**`, where the stub credentials that fire `S105` have no true positive). `pyright`
|
||||
stayed ungated: its only findings are the `etv_client` imports in the row above, and gating it would
|
||||
put a node toolchain on the git-only `small` lane to find nothing. Rationale and the exemption list:
|
||||
`script-tests` job runs `ruff check .` + `ruff format --check .` against a pinned `ruff==0.12.11`,
|
||||
as its first steps. The pre-fix state, reproducible rather than asserted — against `706674272`, the
|
||||
base this landed on, with the committed config dropped in:
|
||||
|
||||
```
|
||||
git archive 706674272 | tar -x -C /tmp/m && git show main:ruff.toml > /tmp/m/ruff.toml
|
||||
cd /tmp/m && ruff check . && ruff format --check . # 72 errors; 20 of 33 files would be reformatted
|
||||
```
|
||||
|
||||
The row above measured **47** eight days earlier against the operator's global config; the tree grew
|
||||
and the configs differ, so the two numbers are not comparable and neither supersedes the other. Of the
|
||||
72, **54 were fixed in code** (most of them by the `ruff format` pass itself, which splits the 40
|
||||
semicolon statements) and **18 carry a per-site `# noqa` with its reason inline**: 8 `S105` on stub
|
||||
credentials handed to the real hooks by `scripts/tests`, 9 `E501` on one-line JSON fixtures, 1
|
||||
`UP031` on a template whose payload contains literal braces. The `S105`s are deliberately per-site
|
||||
rather than a directory exemption, so a real credential pasted into a fixture later still reddens the
|
||||
gate. Only `S101` is exempted directory-wide for `scripts/tests/**`, because a test suite asserts. `pyright` stayed ungated: its only findings are the
|
||||
`etv_client` imports in the row above, and gating it would put a node toolchain on the git-only
|
||||
`small` lane to find nothing. Rationale and the exemption list:
|
||||
`ci.python-lint-ruff-config-committed`.
|
||||
|
||||
### 5.3 Configured vs actually invoked
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
# passed on a fast laptop and flaked on a starved CI VM). The settings below are pinned HERE so the
|
||||
# lint verdict is a property of the repo, not of the machine.
|
||||
#
|
||||
# It is enforced by the `Script lint (ruff)` job in .gitea/workflows/pr-checks.yml, which runs the
|
||||
# same two commands this file configures. A config nobody runs is the same divergence one step later.
|
||||
# It is enforced by the `script-tests` job (`Script lint and tests (ruff + pytest)`) in
|
||||
# .gitea/workflows/pr-checks.yml, which runs the same two commands this file configures. A config
|
||||
# nobody runs is the same divergence one step later.
|
||||
#
|
||||
# `pyright` is deliberately NOT gated: its only findings here are `reportMissingImports` for
|
||||
# `etv_client` in scripts/scripted-schedules/entrypoint.py, which resolves only inside that script's
|
||||
@@ -35,10 +36,12 @@ ignore = [
|
||||
]
|
||||
|
||||
[lint.per-file-ignores]
|
||||
# scripts/tests execs the real shell scripts and hooks, so it asserts (S101) and hands them
|
||||
# deliberately fake credentials (`env["ETV_GITEA_TOKEN"] = "stub"`). S105 fires on every one of those
|
||||
# and has no true positive in this tree — the real credentials come from the environment.
|
||||
"scripts/tests/**" = ["S101", "S105"]
|
||||
# scripts/tests asserts, so S101 would fire on every test. S105 is deliberately NOT exempted here:
|
||||
# the eight sites that trip it (`env["ETV_GITEA_TOKEN"] = "stub"`) carry a per-site `# noqa: S105`
|
||||
# instead, so a real credential pasted into a fixture next year still reddens the gate. A directory
|
||||
# blanket would have given up hardcoded-credential coverage over the largest Python surface in the
|
||||
# repo, permanently, to suppress eight known lines.
|
||||
"scripts/tests/**" = ["S101"]
|
||||
|
||||
[format]
|
||||
quote-style = "double"
|
||||
|
||||
@@ -241,7 +241,7 @@ def test_a_hook_that_LOSES_its_instrumentation_is_DETECTED():
|
||||
|
||||
def test_begin_placed_AFTER_the_stdin_read_is_DETECTED():
|
||||
"""The subtler mutation: present but too late. Ordering is the property that makes it work."""
|
||||
late = 'set -euo pipefail\ninput=$(cat)\nETV_HOOK_FIRE_LIB="x"\n[ -r "$ETV_HOOK_FIRE_LIB" ] && . "$ETV_HOOK_FIRE_LIB" || true\netv_hook_fire_begin demo "" capture || true\n' # noqa: E501 - one-line JSON/shell fixture; splitting it would change the payload
|
||||
late = 'set -euo pipefail\ninput=$(cat)\nETV_HOOK_FIRE_LIB="x"\n[ -r "$ETV_HOOK_FIRE_LIB" ] && . "$ETV_HOOK_FIRE_LIB" || true\netv_hook_fire_begin demo "" capture || true\n' # noqa: E501 - kept on one line so the fixture is greppable against the payload the hook receives
|
||||
faults = instrumentation_faults(late, "demo")
|
||||
assert any("reads stdin before" in f for f in faults), (
|
||||
"a begin call placed after `input=$(cat)` was accepted. It would record a fire with no "
|
||||
@@ -263,15 +263,15 @@ def test_begin_placed_AFTER_the_stdin_read_is_DETECTED():
|
||||
# issue `deny`. An A/B over silent allows proves transparency on the one path where there is nothing
|
||||
# to be transparent about.
|
||||
PAYLOADS = {
|
||||
"bash-deny": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"%(cwd)s","tool_input":{"command":"ETV_UPDATE_GOLDENS=1 dotnet test"}}', # noqa: E501 - one-line JSON/shell fixture; splitting it would change the payload
|
||||
"bash-allow": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"%(cwd)s","tool_input":{"command":"ls -la"}}', # noqa: E501 - one-line JSON/shell fixture; splitting it would change the payload
|
||||
"git-commit": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"%(cwd)s","tool_input":{"command":"git commit -m x"}}', # noqa: E501 - one-line JSON/shell fixture; splitting it would change the payload
|
||||
"bash-deny": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"%(cwd)s","tool_input":{"command":"ETV_UPDATE_GOLDENS=1 dotnet test"}}', # noqa: E501 - kept on one line so the fixture is greppable against the payload the hook receives
|
||||
"bash-allow": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"%(cwd)s","tool_input":{"command":"ls -la"}}', # noqa: E501 - kept on one line so the fixture is greppable against the payload the hook receives
|
||||
"git-commit": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"%(cwd)s","tool_input":{"command":"git commit -m x"}}', # noqa: E501 - kept on one line so the fixture is greppable against the payload the hook receives
|
||||
"nav-deny": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"mcp__x__navigate","cwd":"%(cwd)s","tool_input":{"url":"http://h/iptv/channels.m3u"}}',
|
||||
"agent-no-model": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Agent","cwd":"%(cwd)s","tool_input":{"description":"d","prompt":"p"}}', # noqa: E501 - one-line JSON/shell fixture; splitting it would change the payload
|
||||
"agent-model": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Agent","cwd":"%(cwd)s","tool_input":{"description":"d","prompt":"p","model":"sonnet"}}', # noqa: E501 - one-line JSON/shell fixture; splitting it would change the payload
|
||||
"merge": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"mcp__gitea__pull_request_write","cwd":"%(cwd)s","tool_input":{"method":"merge","owner":"timothy","repo":"ersatztv","pull_number":1}}', # noqa: E501 - one-line JSON/shell fixture; splitting it would change the payload
|
||||
"worktree-add": '{"session_id":"s","hook_event_name":"PostToolUse","tool_name":"Bash","cwd":"%(cwd)s","tool_input":{"command":"git worktree add /tmp/nope-%(nonce)s HEAD"}}', # noqa: E501 - one-line JSON/shell fixture; splitting it would change the payload
|
||||
"ui-edit": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Edit","cwd":"%(cwd)s","tool_input":{"file_path":"web/src/screens/Channels.tsx"}}', # noqa: E501 - one-line JSON/shell fixture; splitting it would change the payload
|
||||
"agent-no-model": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Agent","cwd":"%(cwd)s","tool_input":{"description":"d","prompt":"p"}}', # noqa: E501 - kept on one line so the fixture is greppable against the payload the hook receives
|
||||
"agent-model": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Agent","cwd":"%(cwd)s","tool_input":{"description":"d","prompt":"p","model":"sonnet"}}', # noqa: E501 - kept on one line so the fixture is greppable against the payload the hook receives
|
||||
"merge": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"mcp__gitea__pull_request_write","cwd":"%(cwd)s","tool_input":{"method":"merge","owner":"timothy","repo":"ersatztv","pull_number":1}}', # noqa: E501 - kept on one line so the fixture is greppable against the payload the hook receives
|
||||
"worktree-add": '{"session_id":"s","hook_event_name":"PostToolUse","tool_name":"Bash","cwd":"%(cwd)s","tool_input":{"command":"git worktree add /tmp/nope-%(nonce)s HEAD"}}', # noqa: E501 - kept on one line so the fixture is greppable against the payload the hook receives
|
||||
"ui-edit": '{"session_id":"s","hook_event_name":"PreToolUse","tool_name":"Edit","cwd":"%(cwd)s","tool_input":{"file_path":"web/src/screens/Channels.tsx"}}', # noqa: E501 - kept on one line so the fixture is greppable against the payload the hook receives
|
||||
"empty": "",
|
||||
"garbage": "not json at all",
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ def hook(tmp_path):
|
||||
env["PATH"] = f"{bindir}{os.pathsep}{env['PATH']}"
|
||||
env["STUB_DIR"] = str(state)
|
||||
env["STUB_SHA"] = SHA
|
||||
env["ETV_GITEA_TOKEN"] = "stub"
|
||||
env["ETV_GITEA_TOKEN"] = "stub" # noqa: S105 - deliberately fake; the real credential comes from the environment
|
||||
env["ETV_GITEA_URL"] = "http://gitea.example"
|
||||
env.pop("ETV_GITEA_BASICAUTH", None)
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ def hook(tmp_path):
|
||||
env["PATH"] = f"{bindir}{os.pathsep}{env['PATH']}"
|
||||
env["STUB_DIR"] = str(state)
|
||||
env["STUB_SHA"] = SHA
|
||||
env["ETV_GITEA_TOKEN"] = "stub"
|
||||
env["ETV_GITEA_TOKEN"] = "stub" # noqa: S105 - deliberately fake; the real credential comes from the environment
|
||||
env["ETV_GITEA_URL"] = "http://gitea.example"
|
||||
env.pop("ETV_GITEA_BASICAUTH", None)
|
||||
|
||||
@@ -332,7 +332,7 @@ def hook_jq16(tmp_path):
|
||||
env["PATH"] = f"{bindir}{os.pathsep}{env['PATH']}"
|
||||
env["STUB_DIR"] = str(state)
|
||||
env["STUB_SHA"] = SHA
|
||||
env["ETV_GITEA_TOKEN"] = "stub"
|
||||
env["ETV_GITEA_TOKEN"] = "stub" # noqa: S105 - deliberately fake; the real credential comes from the environment
|
||||
env["ETV_GITEA_URL"] = "http://gitea.example"
|
||||
env.pop("ETV_GITEA_BASICAUTH", None)
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ def hook(tmp_path):
|
||||
env["STUB_DIR"] = str(state)
|
||||
env["STUB_SHA"] = SHA
|
||||
env["STUB_SHORT"] = SHORT
|
||||
env["ETV_GITEA_TOKEN"] = "stub"
|
||||
env["ETV_GITEA_TOKEN"] = "stub" # noqa: S105 - deliberately fake; the real credential comes from the environment
|
||||
env["ETV_GITEA_URL"] = "http://gitea.example"
|
||||
env["CLAUDE_PROJECT_DIR"] = str(REPO_ROOT)
|
||||
env.pop("ETV_GITEA_BASICAUTH", None)
|
||||
|
||||
@@ -104,7 +104,7 @@ def gitea(tmp_path):
|
||||
env = dict(os.environ)
|
||||
env["PATH"] = f"{bindir}{os.pathsep}{env['PATH']}"
|
||||
env["STUB_DIR"] = str(state)
|
||||
env["ETV_GITEA_TOKEN"] = "stub-token"
|
||||
env["ETV_GITEA_TOKEN"] = "stub-token" # noqa: S105 - deliberately fake; the real credential comes from the environment
|
||||
env["ETV_GITEA_URL"] = "http://gitea.example"
|
||||
env["ETV_GITEA_REPO"] = "timothy/ersatztv"
|
||||
env.pop("ETV_GITEA_BASICAUTH", None)
|
||||
|
||||
@@ -119,7 +119,7 @@ def enumerate_files(tmp_path):
|
||||
env["PATH"] = f"{bindir}{os.pathsep}{env['PATH']}"
|
||||
env["STUB_DIR"] = str(state)
|
||||
env["STUB_SHA"] = SHA
|
||||
env["ETV_GITEA_TOKEN"] = "stub"
|
||||
env["ETV_GITEA_TOKEN"] = "stub" # noqa: S105 - deliberately fake; the real credential comes from the environment
|
||||
env["ETV_GITEA_URL"] = "http://gitea.example"
|
||||
env.pop("ETV_GITEA_BASICAUTH", None)
|
||||
env.pop("GITEA_TOKEN", None)
|
||||
@@ -591,7 +591,7 @@ def _mirror_tree(tmp_path, stub_body):
|
||||
|
||||
env = dict(os.environ)
|
||||
env["PATH"] = f"{bindir}{os.pathsep}{env['PATH']}"
|
||||
env["ETV_GITEA_TOKEN"] = "stub"
|
||||
env["ETV_GITEA_TOKEN"] = "stub" # noqa: S105 - deliberately fake; the real credential comes from the environment
|
||||
env["ETV_GITEA_URL"] = "http://gitea.example"
|
||||
env.pop("ETV_GITEA_BASICAUTH", None)
|
||||
|
||||
@@ -647,7 +647,7 @@ def test_a_MISSING_script_withholds_the_exemption(tmp_path):
|
||||
curl.chmod(0o755)
|
||||
env = dict(os.environ)
|
||||
env["PATH"] = f"{bindir}{os.pathsep}{env['PATH']}"
|
||||
env["ETV_GITEA_TOKEN"] = "stub"
|
||||
env["ETV_GITEA_TOKEN"] = "stub" # noqa: S105 - deliberately fake; the real credential comes from the environment
|
||||
env["ETV_GITEA_URL"] = "http://gitea.example"
|
||||
payload = {"tool_input": {"method": "merge", "owner": "timothy", "repo": "ersatztv", "pull_number": 42}}
|
||||
r = subprocess.run(
|
||||
|
||||
Reference in New Issue
Block a user