#162

modern-python PATH shim silently fails bare python3 in Claude Code hook contexts

Openadelaidasofiaopened 5mo ago

Problem

The modern-python/hooks/setup-shims.sh SessionStart hook prepends a python3 shim to PATH that always exit 1s. This breaks every Claude Code settings.json hook that invokes bare python3 /path/to/hook.py: the hook command silently fails because most are wrapped with 2>/dev/null || true (or rely on the harness treating exit 1 as a soft skip), so the user never sees the shim's stderr nudge.

Concrete impact on my setup (~25 user-defined hooks broken since 1.5.0 landed on my system on 2026-05-09):

  • Stop hooks (worktree-archive-autoprep, check-fabricated-hook-attribution, check-fabricated-panelist)
  • SessionEnd hooks (reconcile-worktree-shared)
  • PreToolUse hooks (vault-command-nudges, block-raw-vault-git, several others)
  • PostToolUse hooks (hookify-auto-commit, etc.)

The shim's intent is a teaching nudge for human-typed python3 -m pip install. In automated contexts, no human reads the nudge. The cost is silent breakage.

Repro

  1. Install trailofbits/modern-python plugin.
  2. Add any user hook to ~/.claude/settings.json that runs python3 /path/to/script.py.
  3. Trigger the hook. Script never executes; stderr goes to harness log only.

Suggested fix

Bypass the shim when CLAUDECODE=1 (Claude Code sets this in every subprocess: hooks, Bash tool, agents). The nudge still fires in plain terminal shells where the user typed python3 ... directly without invoking Claude Code.

Verified patch against 1.5.0 hooks/shims/python (top of file, before the case statement):

if [[ "${CLAUDECODE:-}" == "1" ]]; then
  shim_dir="${BASH_SOURCE[0]%/*}"
  shim_dir="$(cd "$shim_dir" && pwd)"
  clean_path=":$PATH:"
  clean_path="${clean_path//:$shim_dir:/:}"
  clean_path="${clean_path#:}"
  clean_path="${clean_path%:}"
  PATH="$clean_path" exec "$cmd" "$@"
fi

If preferred, narrower variants:

  • Skip only on python3 /absolute/path/to/script.py invocations (let -m, -c keep nudging).
  • Skip when stdin is not a tty.

CLAUDECODE=1 is the cleanest because it precisely targets the harm domain (Claude Code automation) without affecting the nudge's intended audience (humans typing in terminal).

Happy to send a PR.

Environment

  • macOS, Claude Code 2.1.128
  • modern-python plugin 1.5.0 (installed 2026-05-09 23:27 local)
  • Affected hook surfaces: PreToolUse, PostToolUse, Stop, SessionStart, SessionEnd, UserPromptSubmit

Activity

5
antonbabenko3mo ago

Confirming this on macOS, plus some measurements that refine the diagnosis and, I think, change what the right upstream fix is.

Environment: Claude Code 2.1.220, modern-python 1.5.2, macOS 15, uv 0.11.32, login shell zsh 5.9.

What actually happens on this version

I traced how the CLAUDE_ENV_FILE line gets applied:

  • Each SessionStart hook is handed its own env file at ~/.claude/session-env/<session-id>/sessionstart-hook-<N>.sh. The plugin's is a single export PATH="<shims>:${PATH}".
  • Claude Code concatenates every such file, sorted by event class (setup < sessionstart < cwdchanged < filechanged) and then by numeric index, and prepends the result to each Bash tool command string. Later lines win the PATH race.
  • Hook subprocesses are spawned from the parent process environment. As far as I can tell from the binary and from observing running processes, that concatenated preamble is not injected into hook spawns on 2.1.220.

So on this version the blast radius is Claude's own Bash tool calls, not hooks directly. A hook of mine that probes python3 -c '' works fine when Claude Code is launched normally from a terminal.

That said, the reported hook breakage is real and reachable: hooks inherit the parent process PATH, so any Claude Code launched from an already-poisoned shell propagates the broken PATH to all of its hooks. I reproduced exactly that by starting a nested claude -p from a Bash tool call - its SessionEnd hook died with the shim error. If you launch claude from a wrapper script, a nested agent, or a shell that sourced the env file, you get the reported symptom.

Three details worth knowing

1. The Bash tool shell here is zsh, not bash.

$0=/bin/zsh   ZSH_VERSION=5.9   BASH_VERSION=<unset>

Anything written to CLAUDE_ENV_FILE has to be POSIX. read -ra, printf -v, export -f, and trap -p are all bash-only and fail silently or loudly here. Worth keeping in mind for any fix that emits shell code.

2. Non-shell-builtin children inherit the poisoned PATH. This is the assertion that matters, and it rules out shell-function wrappers as a workaround:

$ /bin/sh -c 'command -v python3'
.../modern-python/1.5.2/hooks/shims/python3

Anything Claude launches indirectly is affected: pre-commit style hook runners, Makefile recipes, tox, pipx run based tools, #!/usr/bin/env python3 scripts. A pre-commit hook whose entry is bare python3 -c ... fails with ERROR: Use \uv run python3 ...`` and reads to the operator like a genuine hook failure.

3. The shims are not uniform. hooks/shims/uv is well behaved: it intercepts only uv pip and execs the real uv for everything else. python, python3, pip, pip3, pipx never exec anything - they print and exit 1 on every invocation, pipx run included.

Why the proposed CLAUDECODE=1 bypass is a no-op

The stated goal is to keep the teaching nudge for interactive terminal use and skip it in automation. But setup-shims.sh exits early unless CLAUDE_ENV_FILE is set, and that variable is only ever set by Claude Code. The shims therefore never reach an interactive human shell in the first place - the only consumer is the agent. Gating on CLAUDECODE=1 removes the nudge everywhere it currently exists, which is the same outcome as deleting the shims, just less obviously.

Suggestion

If the goal is to keep teaching without breaking automation, the lever is which invocations fail, not which context:

  • Let python / python3 exec the real interpreter. Running an interpreter is not the mistake the plugin is trying to prevent, and it is the invocation most likely to be reached indirectly.
  • Keep the hard failure for the genuinely environment-mutating commands: pip install/uninstall, pipx install/inject/upgrade, python -m pip.
  • Consider passing pipx run through, since it is the tool-execution path rather than an install path, and it is what pipx-based hook runners call.
  • Optionally print the nudge to stderr and still exec, for the passthrough cases.

The skill text is loaded into the agent's context anyway, so it carries the "use uv" guidance regardless of whether a shim fails.

Local workaround

For anyone who wants python working now without disabling the plugin or editing the vendored copy: add a counter-hook that strips the shims directory back out of PATH. The plugin and its skill stay enabled.

1. ~/.claude/hooks/strip-modern-python-shims.sh (chmod +x):

#!/usr/bin/env bash
set -euo pipefail
# Counter-hook for trailofbits/skills#162.
# The emitted snippet MUST be POSIX: the Bash tool shell may be zsh, which has
# no `read -ra`, no `printf -v`, no `export -f`, and no `trap -p`.
[[ -n "${CLAUDE_ENV_FILE:-}" ]] || exit 0

# Leading newline: defensive, so we can never splice onto a previous line.
printf '\n' >>"$CLAUDE_ENV_FILE"
cat >>"$CLAUDE_ENV_FILE" <<'SNIPPET'
# --- trailofbits/skills#162: drop modern-python's fail-only PATH shims ---
__ctf_mp_filter() {
  # Cheap gate: one pattern test, no loop, no forks. This is what runs before
  # every command once PATH is already clean.
  case ":$PATH:" in
    *"/modern-python/"*"/hooks/shims"*) ;;
    *) return 0 ;;
  esac

  __ctf_rest="${PATH:-}"; __ctf_kept=""; __ctf_first=1
  while :; do
    __ctf_d="${__ctf_rest%%:*}"
    case "$__ctf_d" in
      */modern-python/*/hooks/shims|*/modern-python/*/hooks/shims/) ;;
      *)
        if [ "$__ctf_first" = 1 ]; then __ctf_kept="$__ctf_d"; __ctf_first=0
        else __ctf_kept="$__ctf_kept:$__ctf_d"; fi ;;
    esac
    case "$__ctf_rest" in *:*) __ctf_rest="${__ctf_rest#*:}" ;; *) break ;; esac
  done
  PATH="$__ctf_kept"
  export PATH
  unset __ctf_rest __ctf_kept __ctf_first __ctf_d

  # Fail loud, once: the gate matched but the per-entry walk removed nothing,
  # so the plugin's layout no longer fits the pattern above.
  case ":$PATH:" in
    *"/modern-python/"*"/hooks/shims"*)
      if [ -z "${__ctf_warned:-}" ]; then
        __ctf_warned=1
        echo "warn: modern-python shims survived the PATH filter" >&2
      fi ;;
  esac
}

__ctf_mp_guard() {
  [ -n "${__ctf_busy:-}" ] && return 0
  __ctf_busy=1
  __ctf_mp_filter
  unset __ctf_busy
}

__ctf_mp_filter                # immediate pass; handles favorable hook ordering
# Stays armed for the life of the shell ON PURPOSE. See note below.
trap '__ctf_mp_guard' DEBUG
SNIPPET

2. Register it in ~/.claude/settings.json (validate with jq empty afterwards - a syntax error there silently disables every SessionStart hook):

"SessionStart": [
  {
    "matcher": "",
    "hooks": [
      { "type": "command", "command": "bash \"$HOME/.claude/hooks/strip-modern-python-shims.sh\"" }
    ]
  }
]

3. Start a new session. A resumed session reuses its original env directory, so the fix will not appear there. To patch an already-running session in place, write the snippet into that session's directory at an index that sorts after the plugin's:

CLAUDE_ENV_FILE=~/.claude/session-env/<session-id>/sessionstart-hook-9.sh \
  bash ~/.claude/hooks/strip-modern-python-shims.sh

Two things that a simpler version of this gets wrong

The hook ordering is adverse in practice, so a plain PATH filter is not enough. Measured after install: my counter-hook landed at sessionstart-hook-1.sh and the plugin at sessionstart-hook-4.sh, meaning the plugin's export is concatenated after mine. A one-shot filter loses that race on every session. The DEBUG trap is what makes it order independent: it fires after the whole preamble has been sourced, and because it runs in the parent shell before the command is spawned, every child inherits the cleaned PATH. Index assignment across settings and plugin sources is undocumented and I have seen it vary (2, 3, 4) across sessions, so this is not something to verify once and rely on.

Do not make the trap self-disarming. I tried that first, to avoid running a filter before every command. It passes under bash and fails under zsh: the trap fires before the poisoning assignment, observes a clean PATH, disarms, and the plugin's export then stands unopposed. Keeping it armed and putting a single case pattern test at the top of the filter is what makes the always-on cost acceptable - one builtin, no loop and no forks, on the already-clean path.

Verification

In a fresh session, all of these should hold:

echo "$PATH" | tr : '\n' | grep 'modern-python.*hooks/shims'   # no output
command -v python3            # real interpreter, not the shim
python3 --version             # exit 0
python3 -c 'print(1)'         # 1
pip --version                 # real pip, or an ordinary "command not found"
uv --version                  # unchanged
uv run python -c 'print(1)'   # 1
/bin/sh -c 'command -v python3'   # not the shim, this is the one that fails before the fix

Rollback is removing the SessionStart block and starting a new session. No other state is created.

antonbabenko3mo ago

Confirming this on macOS, plus some measurements that refine the diagnosis and, I think, change what the right upstream fix is.

Environment: Claude Code 2.1.220, modern-python 1.5.2, macOS 15, uv 0.11.32, login shell zsh 5.9.

What actually happens on this version

I traced how the CLAUDE_ENV_FILE line gets applied:

  • Each SessionStart hook is handed its own env file at ~/.claude/session-env/<session-id>/sessionstart-hook-<N>.sh. The plugin's is a single export PATH="<shims>:${PATH}".
  • Claude Code concatenates every such file, sorted by event class (setup < sessionstart < cwdchanged < filechanged) and then by numeric index, and prepends the result to each Bash tool command string. Later lines win the PATH race.
  • Hook subprocesses are spawned from the parent process environment. As far as I can tell from the binary and from observing running processes, that concatenated preamble is not injected into hook spawns on 2.1.220.

So on this version the blast radius is Claude's own Bash tool calls, not hooks directly. A hook of mine that probes python3 -c '' works fine when Claude Code is launched normally from a terminal.

That said, the reported hook breakage is real and reachable: hooks inherit the parent process PATH, so any Claude Code launched from an already-poisoned shell propagates the broken PATH to all of its hooks. I reproduced exactly that by starting a nested claude -p from a Bash tool call - its SessionEnd hook died with the shim error. If you launch claude from a wrapper script, a nested agent, or a shell that sourced the env file, you get the reported symptom.

Three details worth knowing

1. The Bash tool shell here is zsh, not bash.

$0=/bin/zsh   ZSH_VERSION=5.9   BASH_VERSION=<unset>

Anything written to CLAUDE_ENV_FILE has to be POSIX. read -ra, printf -v, export -f, and trap -p are all bash-only and fail silently or loudly here. Worth keeping in mind for any fix that emits shell code.

2. Non-shell-builtin children inherit the poisoned PATH. This is the assertion that matters, and it rules out shell-function wrappers as a workaround:

$ /bin/sh -c 'command -v python3'
.../modern-python/1.5.2/hooks/shims/python3

Anything Claude launches indirectly is affected: pre-commit style hook runners, Makefile recipes, tox, pipx run based tools, #!/usr/bin/env python3 scripts. A pre-commit hook whose entry is bare python3 -c ... fails with ERROR: Use \uv run python3 ...`` and reads to the operator like a genuine hook failure.

3. The shims are not uniform. hooks/shims/uv is well behaved: it intercepts only uv pip and execs the real uv for everything else. python, python3, pip, pip3, pipx never exec anything - they print and exit 1 on every invocation, pipx run included.

Why the proposed CLAUDECODE=1 bypass is a no-op

The stated goal is to keep the teaching nudge for interactive terminal use and skip it in automation. But setup-shims.sh exits early unless CLAUDE_ENV_FILE is set, and that variable is only ever set by Claude Code. The shims therefore never reach an interactive human shell in the first place - the only consumer is the agent. Gating on CLAUDECODE=1 removes the nudge everywhere it currently exists, which is the same outcome as deleting the shims, just less obviously.

Suggestion

If the goal is to keep teaching without breaking automation, the lever is which invocations fail, not which context:

  • Let python / python3 exec the real interpreter. Running an interpreter is not the mistake the plugin is trying to prevent, and it is the invocation most likely to be reached indirectly.
  • Keep the hard failure for the genuinely environment-mutating commands: pip install/uninstall, pipx install/inject/upgrade, python -m pip.
  • Consider passing pipx run through, since it is the tool-execution path rather than an install path, and it is what pipx-based hook runners call.
  • Optionally print the nudge to stderr and still exec, for the passthrough cases.

The skill text is loaded into the agent's context anyway, so it carries the "use uv" guidance regardless of whether a shim fails.

Local workaround

For anyone who wants python working now without disabling the plugin or editing the vendored copy: add a counter-hook that strips the shims directory back out of PATH. The plugin and its skill stay enabled.

1. ~/.claude/hooks/strip-modern-python-shims.sh (chmod +x):

#!/usr/bin/env bash
set -euo pipefail
# Counter-hook for trailofbits/skills#162.
# The emitted snippet MUST be POSIX: the Bash tool shell may be zsh, which has
# no `read -ra`, no `printf -v`, no `export -f`, and no `trap -p`.
[[ -n "${CLAUDE_ENV_FILE:-}" ]] || exit 0

# Leading newline: defensive, so we can never splice onto a previous line.
printf '\n' >>"$CLAUDE_ENV_FILE"
cat >>"$CLAUDE_ENV_FILE" <<'SNIPPET'
# --- trailofbits/skills#162: drop modern-python's fail-only PATH shims ---
__ctf_mp_filter() {
  # Cheap gate: one pattern test, no loop, no forks. This is what runs before
  # every command once PATH is already clean.
  case ":$PATH:" in
    *"/modern-python/"*"/hooks/shims"*) ;;
    *) return 0 ;;
  esac

  __ctf_rest="${PATH:-}"; __ctf_kept=""; __ctf_first=1
  while :; do
    __ctf_d="${__ctf_rest%%:*}"
    case "$__ctf_d" in
      */modern-python/*/hooks/shims|*/modern-python/*/hooks/shims/) ;;
      *)
        if [ "$__ctf_first" = 1 ]; then __ctf_kept="$__ctf_d"; __ctf_first=0
        else __ctf_kept="$__ctf_kept:$__ctf_d"; fi ;;
    esac
    case "$__ctf_rest" in *:*) __ctf_rest="${__ctf_rest#*:}" ;; *) break ;; esac
  done
  PATH="$__ctf_kept"
  export PATH
  unset __ctf_rest __ctf_kept __ctf_first __ctf_d

  # Fail loud, once: the gate matched but the per-entry walk removed nothing,
  # so the plugin's layout no longer fits the pattern above.
  case ":$PATH:" in
    *"/modern-python/"*"/hooks/shims"*)
      if [ -z "${__ctf_warned:-}" ]; then
        __ctf_warned=1
        echo "warn: modern-python shims survived the PATH filter" >&2
      fi ;;
  esac
}

__ctf_mp_guard() {
  [ -n "${__ctf_busy:-}" ] && return 0
  __ctf_busy=1
  __ctf_mp_filter
  unset __ctf_busy
}

__ctf_mp_filter                # immediate pass; handles favorable hook ordering
# Stays armed for the life of the shell ON PURPOSE. See note below.
trap '__ctf_mp_guard' DEBUG
SNIPPET

2. Register it in ~/.claude/settings.json (validate with jq empty afterwards - a syntax error there silently disables every SessionStart hook):

"SessionStart": [
  {
    "matcher": "",
    "hooks": [
      { "type": "command", "command": "bash \"$HOME/.claude/hooks/strip-modern-python-shims.sh\"" }
    ]
  }
]

3. Start a new session. A resumed session reuses its original env directory, so the fix will not appear there. To patch an already-running session in place, write the snippet into that session's directory at an index that sorts after the plugin's:

CLAUDE_ENV_FILE=~/.claude/session-env/<session-id>/sessionstart-hook-9.sh \
  bash ~/.claude/hooks/strip-modern-python-shims.sh

Two things that a simpler version of this gets wrong

The hook ordering is adverse in practice, so a plain PATH filter is not enough. Measured after install: my counter-hook landed at sessionstart-hook-1.sh and the plugin at sessionstart-hook-4.sh, meaning the plugin's export is concatenated after mine. A one-shot filter loses that race on every session. The DEBUG trap is what makes it order independent: it fires after the whole preamble has been sourced, and because it runs in the parent shell before the command is spawned, every child inherits the cleaned PATH. Index assignment across settings and plugin sources is undocumented and I have seen it vary (2, 3, 4) across sessions, so this is not something to verify once and rely on.

Do not make the trap self-disarming. I tried that first, to avoid running a filter before every command. It passes under bash and fails under zsh: the trap fires before the poisoning assignment, observes a clean PATH, disarms, and the plugin's export then stands unopposed. Keeping it armed and putting a single case pattern test at the top of the filter is what makes the always-on cost acceptable - one builtin, no loop and no forks, on the already-clean path.

Verification

In a fresh session, all of these should hold:

echo "$PATH" | tr : '\n' | grep 'modern-python.*hooks/shims'   # no output
command -v python3            # real interpreter, not the shim
python3 --version             # exit 0
python3 -c 'print(1)'         # 1
pip --version                 # real pip, or an ordinary "command not found"
uv --version                  # unchanged
uv run python -c 'print(1)'   # 1
/bin/sh -c 'command -v python3'   # not the shim, this is the one that fails before the fix

Rollback is removing the SessionStart block and starting a new session. No other state is created.

dguido2mo ago

Flagging that #255 (modern-python 1.6.0) narrowed these shims but does not fix this issue, so nobody reads that change and assumes it did.

What 1.6.0 changed: python -c, python -m <module> and python - now pass through to the real interpreter, and uv pip passes through when it carries --project, --directory, --target or -t. That was #207.

Your case is python3 /path/to/script.py from a hook, and a bare script path is still intercepted — deliberately, since that is the invocation uv run python exists to replace. So the ~25 hooks described here still break, and still break silently wherever the hook discards stderr.

The underlying disagreement this issue raises is unresolved and needs a decision before anyone can close it. This issue argues the shim should be inert inside Claude Code, because no human reads the nudge in an automated context. plugins/modern-python/README.md argues the opposite: catching Claude is the stated purpose of the shims. A CLAUDECODE=1 bypass would satisfy this issue and defeat the README's intent; keeping the current behavior does the reverse. Whoever picks that up has to settle which audience the shims serve, not just apply the patch.

One detail from #207 that is relevant here: the shims are on PATH, so they intercept every subprocess any tool spawns, not only commands Claude types. That is exactly why hook scripts are caught, and it is the property that makes the "coach the agent" framing leaky.

sushant-me28d ago

I went to write the suggested patch and stopped, because applying it as written looks like it would turn the shim off everywhere rather than narrowing it. Two things I checked:

1. Claude Code is the shim's only audience, not an exception to it.

hooks/setup-shims.sh installs the shim by writing its directory to $CLAUDE_ENV_FILE:

echo "export PATH=\"${shims_dir}:\${PATH}\"" >>"$CLAUDE_ENV_FILE"

and it bails when that variable is unset:

if [[ -z "${CLAUDE_ENV_FILE:-}" ]]; then
  echo "modern-python: CLAUDE_ENV_FILE not set; shims will not be installed" >&2
  exit 0
fi

CLAUDE_ENV_FILE is set by Claude Code, not by a plain terminal shell. So outside Claude Code the shim is not on PATH at all — the nudge cannot fire in a plain terminal today, and it would not "still fire" there after this patch either. CLAUDECODE=1 is not the automated-context special case; it is the whole installed footprint.

2. The refusal is tested behaviour, and #207 set a different precedent.

python-shim.bats asserts the nudge directly:

@test "exits non-zero for bare python"            -> output contains "uv run python"
@test "exits non-zero for python script.py"       -> output contains "uv run python script.py"

So bypassing on CLAUDECODE=1 would fail the shim's own tests and remove the thing the shim exists to do.

And this is the same class of problem as #207 ("modern-python shims break prek hook installation and python3 -"), which the shim already cites in a comment. That was resolved by narrowing which invocations are intercepted — -c, -m and - pass through, a script path still refuses:

# `-c`, `-m` and `-` are not that. They read a program from the command line, a module
# already on the path, or stdin, and none of them resolves a script's dependencies.

i.e. by argument shape, never by environment. Your own "narrower variant" — skip on python3 /absolute/path/to/script.py and let -m/-c keep nudging — is the one that matches how this was settled before.

The defect is real; I am only arguing about the discriminator. What actually breaks is that a user settings.json hook runs python3 /abs/hook.py, the shim exits 1, and the harness swallows it. An absolute script path is what hook configs use (they must work from any cwd) and is not how the agent tends to invoke a script inside a project, so it separates the two cases on the axis #207 already established.

I have not opened a PR for this yet, because the discriminator is a call I would rather you make than guess at — CLAUDECODE, an absolute script path, a non-tty stdin, or something else. Tell me which and I will write it against these tests.

sushant-me28d ago

Correction to my comment above: it was already here, and I should have read the thread before posting.

@antonbabenko made the CLAUDE_ENV_FILE argument on 2026-07-28, under the heading "Why the proposed CLAUDECODE=1 bypass is a no-op":

"setup-shims.sh exits early unless CLAUDE_ENV_FILE is set, and that variable is only ever set by Claude Code. The shims therefore never reach an interactive human shell in the first place - the only consumer is the agent. Gating on CLAUDECODE=1 removes the nudge everywhere it currently exists, which is the same outcome as deleting the shims, just less obviously."

That is the point I posted as a find. It is theirs, three months earlier.

And @dguido had already framed the decision on 2026-08-19:

"The underlying disagreement this issue raises is unresolved and needs a decision before anyone can close it. ... Whoever picks that up has to settle which audience the shims serve, not just apply the patch."

So my "this is a call I would rather you make than guess at" was restating a call that was already identified and explicitly parked for a maintainer. I added a third voice to a two-voice conclusion and implied it was new.

What I did wrong: I fetched the issue body (--json title,body) and the code, but never --json comments. I checked the source against the report and treated the report as the whole discussion. On an issue that had been open four months with two substantive comments, that is exactly the "concluded without verifying" failure I keep correcting elsewhere.

What I would add, if anything, is narrower than what I wrote. @antonbabenko's measurements and the original report disagree on where the breakage lands on current versions: they found the concatenated preamble is not injected into hook spawns on 2.1.220 and that hooks break mainly via an already-poisoned parent PATH, while the report describes direct hook breakage. That distinction decides whether a fix in the shim is even the right layer, and it is testable by whoever has the affected setup. I cannot test it here.

I am not proposing a fix. The audience question is a maintainer decision, both prior comments say so, and nothing I can verify resolves it.