tea-guard: resolve and rewrite --login instead of env-checking

The previous guard required $GITEA_LOGIN to be set in the Bash environment,
which (a) only happens after a session restart and (b) let Claude name any
login it liked as long as one was set. Two failures: pinning needed a restart
to take effect, and Claude could pick the wrong identity from memory.

Rewrite the guard (now python3 for JSON in/out) to RESOLVE the login itself:

- Claude must write the literal placeholder --login "$GITEA_LOGIN".
- The hook reads the operator's pin from .claude/settings.local.json
  (env.GITEA_LOGIN) at call time — from the FILE via CLAUDE_PROJECT_DIR/cwd
  walk-up — and rewrites the command to that literal via updatedInput.
- A literal login, another variable, an empty value, or a missing --login are
  all blocked: Claude may not choose the identity, only the operator may.
- No pin -> block with a pointer to /tea:login.

Effect: pinning works in the same session (no restart), and Claude can no
longer act under a login it picked. /tea:login now mandates an explicit
operator choice (AskUserQuestion), never inferring from memory. /tea:use
documents the placeholder-only contract.

Guard unit-tested across 13 rewrite/block/passthrough cases incl. -l,
--login=, ${...}, compound+walkup+pipe. claude plugin validate passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
naudachu
2026-05-30 16:26:54 +05:00
parent d4aa0a9038
commit bac45028bf
3 changed files with 188 additions and 78 deletions
+33 -22
View File
@@ -1,40 +1,51 @@
---
name: login
description: Pin the Gitea login used by the tea CLI in this project. Run when $GITEA_LOGIN is unset, when the tea-guard hook blocks a command demanding a login, or when the user types /tea:login. Enumerates available logins, lets the user pick one, and persists it to .claude/settings.local.json.
description: Pin the Gitea login used by the tea CLI in this project. Run when the tea-guard hook reports no login is pinned, or when the user types /tea:login. Enumerates available logins, makes the OPERATOR pick one, and persists it to .claude/settings.local.json. The pin takes effect immediately — no restart.
---
# /tea:login — pin the project Gitea login
Goal: select exactly one `tea` login for this project and persist it to
`.claude/settings.local.json` under `env.GITEA_LOGIN`, so every later `tea`
call can pass `--login "$GITEA_LOGIN"`. The `tea-guard` hook blocks every
Gitea-touching `tea` command until this is done — this command is the
remediation it points to.
Goal: have the **operator** select exactly one `tea` login for this project and
persist it to `.claude/settings.local.json` under `env.GITEA_LOGIN`. The
`tea-guard` hook reads this file at call time and rewrites every
`--login "$GITEA_LOGIN"` to the pinned value, so the choice takes effect
**immediately, with no session restart**.
## The one hard rule: the operator chooses, never you
Picking the wrong identity is the exact failure this command exists to prevent.
So:
- **ALWAYS** present the choice with `AskUserQuestion` and let the operator
pick — even if memory, context, the repo URL, or a previous session suggests
a "likely" login. Do **not** auto-select from memory or infer it. A wrong
guess writes under the wrong account.
- The only exception: exactly **one** login exists on the machine — then
propose it and still confirm before writing.
## Steps
1. Enumerate logins (allowed by the guard even without `--login`):
1. Enumerate logins (allowed by the guard even with no pin):
`tea logins list -o json`
2. **No logins:** stop and ask the user to run `tea logins add` themselves
it is interactive (prompts for URL/token). Do not run it for them.
3. **One login:** propose pinning it; confirm with the user before writing.
4. **Several logins:** use `AskUserQuestion` to let the user pick. Show each
login's `name`, `user`, and `url` so the choice is unambiguous.
5. Merge the chosen name into `.claude/settings.local.json` under `env`
do not clobber other keys:
2. **No logins:** stop and ask the operator to run `tea logins add` themselves
it is interactive (prompts for URL/token). Do not run it for them.
3. **One login:** propose it; confirm before writing.
4. **Several logins:** `AskUserQuestion` with each login's `name`, `user`, and
`url` so the operator's choice is unambiguous. Never decide for them.
5. Merge the chosen name into `.claude/settings.local.json` under `env`
(do not clobber other keys):
```json
{ "env": { "GITEA_LOGIN": "<chosen-name>" } }
```
6. Tell the user: the updated `$GITEA_LOGIN` is only exported into Bash after a
**session restart**. Until they restart, pass the literal name explicitly:
`tea --login <chosen-name> ...`.
6. Done — it is live. The guard resolves the pin from the file on the next
`tea` call; no restart needed. Tell the operator which login is now pinned.
## Hard rules (identity safety)
## Identity-safety rules
- NEVER run commands that mutate logins or global login state:
`tea logins add/edit/delete/default`, `tea logout`. Read-only
`tea logins list` is the only allowed login command.
- If a `tea` call fails with a permission/scope error, report it to the user.
Do NOT try to fix it by switching to, or editing, a different login.
- If you ever see `no gitea login detected, falling back to login '...'`,
treat it as a hard failure: stop, do not act on the result, surface it.
- If a `tea` call fails with a permission/scope error, report it. Do NOT try to
fix it by switching to, or editing, a different login.
- If you ever see `no gitea login detected, falling back to login '...'`, treat
it as a hard failure: stop, do not act on the result, surface it.