name: gh-ghtkn-guard-setup description: Interactively set up, verify, update, or uninstall gh-ghtkn-guard for Claude Code, Codex, or similar coding-agent users so gh uses ghtkn-backed GitHub App User Access Tokens without exposing GH_TOKEN in the agent shell. Use when a user asks to set up the safer gh wrapper, distribute it to a machine, fix agent PATH issues for gh, or revert the wrapper.
gh-ghtkn-guard setup
Use this skill to set up or maintain gh-ghtkn-guard, a host-side gh
wrapper for developers who let Claude Code, Codex, or similar coding agents run
local shell commands. It calls ghtkn get "$GHTKN_APP_NAME" and passes the
resulting token only to the real GitHub CLI child process while blocking
host-side token printing.
Setup Workflow
This skill owns setup. There is no one-shot setup script. Setup must be interactive because it changes shell startup files and PATH resolution.
- Locate or clone the
gh-ghtkn-guardrepository. - Inspect the current machine without making changes:
pwd
command -v gh || true
type gh || true
command -v ghtkn || true
test -f ~/.zshenv && grep -n "gh-ghtkn-guard\\|gh wrapper\\|\\.local/bin" ~/.zshenv || true
- Decide where
GHTKN_APP_NAMEshould come from. Prefer per-ownerdirenvfiles forghqlayouts, for example:
~/ghq/github.com/your-org/.envrc
~/ghq/github.com/your-user/.envrc
If a child repository has its own .envrc, add source_up so the owner-level
value is inherited.
- Present a short setup proposal before editing anything. Include:
- Exact shell startup file that will add the wrapper to PATH, usually
~/.zshenv - Exact
GHTKN_APP_NAMEsource, such as a direnv file or shell startup file - Verification commands that will be run
Ask for explicit approval before changing files.
- After approval, perform setup as individual, inspectable steps:
chmod +x bin/gh
- Add or update this marker block in
~/.zshenv, adjusted to the repository's absolutebinpath. Scope it to coding-agent shells when possible:
# >>> gh-ghtkn-guard PATH >>>
# Route coding-agent `gh` calls through the ghtkn-aware wrapper before Homebrew gh.
if [[ "$CODEX_SHELL" == "1" || "$__CFBundleIdentifier" == "com.openai.codex" ]]; then
_gh_wrapper_bin='/absolute/path/to/gh-ghtkn-guard/bin'
if [[ -d "$_gh_wrapper_bin" ]]; then
path=("$_gh_wrapper_bin" "${(@)path:#$_gh_wrapper_bin}")
export PATH
fi
unset _gh_wrapper_bin
fi
# <<< gh-ghtkn-guard PATH <<<
For Claude Code or a team machine that should always use the wrapper in project shells, a broader shell startup rule is acceptable if the user explicitly wants that behavior.
When PATH prepend is not enough (mise / rbenv / asdf)
The prepend in step 6 runs once at shell startup. Tools that rebuild PATH on
every prompt — mise, rbenv, asdf, and similar — can silently drop the
prepended wrapper entry, because they reconstruct PATH from a snapshot captured
before the prepend ran (mise stores it in __MISE_ORIG_PATH). The symptom:
command -v gh resolves the wrapper right after sourcing startup files, but the
agent's actual shell (e.g. Claude Code's startup snapshot plus the mise precmd
hook) resolves the real gh, with only the wrapper's single PATH entry missing.
Setup looks done, yet every agent gh call hits the logged-out real gh.
The robust fix is to put the wrapper where PATH already points, instead of
fighting the rebuild. Symlink bin/gh into a directory that (a) the version
manager preserves (i.e. it is already in __MISE_ORIG_PATH) and (b) sits
before the real gh's directory (/opt/homebrew/bin) in PATH. ~/.local/bin
is usually both:
ln -s /absolute/path/to/gh-ghtkn-guard/bin/gh ~/.local/bin/gh
The wrapper resolves real gh / ghtkn by absolute path and environment variables
and does not depend on its own location, so a symlink (or copy) placed
anywhere behaves identically. Confirm no earlier PATH directory already provides
a gh (e.g. ~/.opencode/bin/gh, ~/.bun/bin/gh) that would shadow it, then
verify in the agent shell that the wrapper wins even after a PATH rebuild:
command -v gh # must be the wrapper (e.g. ~/.local/bin/gh), not /opt/homebrew/bin/gh
- Configure
GHTKN_APP_NAME, for example:
export GHTKN_APP_NAME=your-org/your-ghtkn-app
- Verify:
type gh
echo "$GHTKN_APP_NAME"
gh api /user --jq .login
gh auth token
/opt/homebrew/bin/gh auth token
Expected:
type ghresolves the wrapper path in the target agent shell.GHTKN_APP_NAMEis set.gh api /usersucceeds.gh auth tokenis blocked bygh-ghtkn-guard.- Direct real
gh auth tokenreturns no token unless the user has separately authenticated the realgh. - If the first
ghcommand times out while waiting forghtkn, runghtkn get "$GHTKN_APP_NAME" >/dev/nullonce in a normal interactive terminal and retry, or enter the one-time code printed above in the GitHub device page. The wrapper does not streamghtknstdout because stdout is reserved for the token, butghtknprints the Device Flow code to stderr.
If the target agent shell resolves the official gh, treat setup as failed and
inspect shell startup files and the agent process environment before continuing.
Install the companion skills (
gh-runner,gh-ghtkn-guard-setup) into the agent's skills directory by symlinking or copying from this repo, which is their single source of truth. Do not hand-edit the installed copy — that is how the two diverge. For Claude Code:ln -snf "$PWD/skills/gh-runner" ~/.claude/skills/gh-runner ln -snf "$PWD/skills/gh-ghtkn-guard-setup" ~/.claude/skills/gh-ghtkn-guard-setup
State check (doctor)
For a machine that already has (some of) the setup, run this in the agent
shell to see how far it has progressed. It is read-only and does not trigger
Device Flow (no gh auth call). In a human's own interactive terminal the
wrapper on PATH line will show the real gh by design — judge from the agent
shell.
printf 'wrapper on PATH : %s\n' "$(command -v gh)"
printf 'real gh present : %s\n' "$(ls /opt/homebrew/bin/gh 2>/dev/null || echo MISSING)"
printf 'ghtkn present : %s\n' "$(command -v ghtkn || echo MISSING)"
printf 'GHTKN_APP_NAME : %s\n' "${GHTKN_APP_NAME:-<unset>}"
printf 'GH_TOKEN in env : %s\n' "${GH_TOKEN:+LEAKED - should be empty}${GH_TOKEN:-clean}"
grep -q gh-ghtkn-guard "$HOME/.zshenv" 2>/dev/null \
&& echo 'zshenv PATH block: present' || echo 'zshenv PATH block: MISSING (steps 5-6)'
for s in gh-runner gh-ghtkn-guard-setup; do
p="$HOME/.claude/skills/$s"
if [ -L "$p" ]; then echo "skill $s: symlink (good)"
elif [ -d "$p" ]; then echo "skill $s: COPY (works but drifts - prefer symlink, step 9)"
else echo "skill $s: NOT INSTALLED (step 9)"; fi
done
Interpretation:
wrapper on PATHshould be the repo'sbin/ghin the agent shell.ghtkn presentand a non-<unset>GHTKN_APP_NAMEmean auth can resolve.GH_TOKEN in envmust readclean. The twoskilllines show whether step 9 was done (symlink), half-done (copy — drifts from the repo source), or skipped.- To test live auth separately, run
gh --version(safe pass-through) then a real read likegh api user --jq .login; the latter starts Device Flow only when no token is cached for the ~8h window.
Uninstall
Run only after telling the user what it removes:
./uninstall.sh
This removes wrapper symlinks, restores the backed-up /usr/local/bin/gh when
present, and removes gh-ghtkn-guard marker blocks from shell startup files.
Safety Notes
- Do not export
GH_TOKENinto long-lived agent shell environments. - Do not bypass the wrapper by calling
gh auth token. - Do not add broad agent permission for direct
ghtknexecution unless the user explicitly wants agents to perform setup or reauthorization themselves. ghtknuses GitHub App User Access Tokens; GitHub controls their expiration.- First authentication of a session (device flow) is agent-driven: the agent runs
the real
ghin the background, surfaces the one-time code, and the human only enters it and authorizes. The agent must not run bareghtkn, must not loop the call, and must not claim a printed code "expired" (it does not know the current time). See thegh-runnerskill, "First authentication in a session".