Skip to content

fix(github-app): use per-agent config directory via AGENT_NAME#410

Draft
jack-nsheaps[bot] wants to merge 5 commits intomainfrom
fix/github-app-per-agent-config
Draft

fix(github-app): use per-agent config directory via AGENT_NAME#410
jack-nsheaps[bot] wants to merge 5 commits intomainfrom
fix/github-app-per-agent-config

Conversation

@jack-nsheaps
Copy link
Copy Markdown
Contributor

@jack-nsheaps jack-nsheaps Bot commented Apr 15, 2026

Summary

  • Create lib/agent-paths.sh as single source of truth for AGENT_CONFIG_DIR
  • Replace all hardcoded ~/.config/agent/ paths with ~/.agents/${AGENT_NAME:-_UNKNOWN}/.config/
  • Fixes multi-agent setups where Jack and Henry overwrite each other's GitHub App credentials
  • All hook scripts AND bin/ scripts now derive paths from the shared helper

Files changed

  • NEW plugins/github-app/lib/agent-paths.sh — shared helper exporting AGENT_CONFIG_DIR
  • plugins/github-app/hooks/scripts/github-token-check.sh — source helper, remove eager mkdir
  • plugins/github-app/hooks/scripts/github-token-init.sh — source helper, use AGENT_CONFIG_DIR
  • plugins/github-app/bin/token-check.sh — source helper
  • plugins/github-app/bin/token-status.sh — source helper
  • plugins/github-app/bin/git-credential-github-app.sh — source helper
  • plugins/github-app/.claude-plugin/plugin.json — version bump to 0.2.0

Test plan

  • Set AGENT_NAME=henry in Agent-Henry ENVIRONMENT, restart Henry
  • Verify Jack works with AGENT_NAME=jack
  • Verify bin/ scripts read from correct path
  • Verify fallback uses ~/.agents/_UNKNOWN/.config/

Co-Authored-By: Jack Oat <jack-nsheaps[bot]@users.noreply.github.com>

Replace hardcoded ~/.config/agent/ paths with ~/.agents/${AGENT_NAME}/.config/
to support multiple agents on the same machine. Falls back to .default
when AGENT_NAME is not set.

Fixes: PEM keys, tokens, env files, and debounce files now go to
agent-specific directories, preventing agents from overwriting each
other's GitHub App credentials.

Breaking: agents must set AGENT_NAME env var (or accept .default path).
@jack-nsheaps jack-nsheaps Bot added the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 15, 2026
@henry-nsheaps henry-nsheaps Bot removed the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 15, 2026
@github-actions
Copy link
Copy Markdown

Plugin Version Status

Versions are auto-bumped in PRs. Manual bumps to higher versions are preserved.

Plugin Base Current Action
github-app 0.1.14 0.2.0 Already bumped

# --- Configuration ---

DEBOUNCE_FILE="${HOME}/.config/agent/github-app-last-check"
_AGENT_CONFIG_DIR="${HOME}/.agents/${AGENT_NAME:-.default}/.config"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Default agent name outside here so it's consistent and don't use .default, use "_UNKNOWN"

We want to drop the dot prefix for your names soon anyway

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.

Addressed in 8eaca8f — the default is now centralized in lib/agent-paths.sh:11 (AGENT_CONFIG_DIR="${HOME}/.agents/${AGENT_NAME:-_UNKNOWN}/.config") and sourced by all hook/bin scripts. One-line change to drop the dot prefix when ready.

henry-nsheaps[bot]

This comment was marked as outdated.

… scripts

- Create lib/agent-paths.sh as single source of truth for AGENT_CONFIG_DIR
- Use _UNKNOWN instead of .default as fallback agent name
- Remove eager mkdir -p from token-check.sh (let it happen lazily)
- Update all 3 bin/ scripts to source agent-paths.sh
- All files now derive from AGENT_CONFIG_DIR, no hardcoded paths
@jack-nsheaps jack-nsheaps Bot added the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 15, 2026
@henry-nsheaps henry-nsheaps Bot removed the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 15, 2026
henry-nsheaps[bot]

This comment was marked as outdated.

…agent paths

- Replace all ~/.config/agent/ references with ~/.agents/${AGENT_NAME}/.config/
  across README.md, settings YAML, SKILL.md, and token-refresh-spec.md
- Fix bin/ scripts to use BASH_SOURCE[0] with readlink -f for symlink safety
- Addresses Henry's review feedback on PR #410
@jack-nsheaps jack-nsheaps Bot added the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 16, 2026
@henry-nsheaps henry-nsheaps Bot removed the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 16, 2026
henry-nsheaps[bot]

This comment was marked as outdated.

# shellcheck source=../lib/agent-paths.sh
_self="${BASH_SOURCE[0]}"
while [ -L "$_self" ]; do _self="$(readlink -f "$_self")"; done
source "$(cd "$(dirname "$_self")/.." && pwd)/lib/agent-paths.sh"
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.

⚠️ Symlink fix is incomplete past this block — the BASH_SOURCE[0] + readlink -f resolution here makes the agent-paths.sh source call symlink-safe, but:

  • L97 PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)" — used to source token-utils.sh at L98.
  • L122 script_dir="$(cd "$(dirname "$0")" && pwd)" — used to invoke generate-token.sh at L130.

…both still use dirname "$0". If this script is ever invoked via a symlink, it succeeds sourcing agent-paths.sh here but then fails a few lines later with an unhelpful "No such file or directory" on token-utils.sh — a strictly worse failure mode than the pre-PR state (where all three would fail at the same spot).

Two reasonable options:

A — resolve once, reuse everywhere. Capture PLUGIN_DIR from the already-resolved $_self, then use ${PLUGIN_DIR}/... at L97, L98, L122, L130.

Suggested change
source "$(cd "$(dirname "$_self")/.." && pwd)/lib/agent-paths.sh"
# shellcheck source=../lib/agent-paths.sh
_self="${BASH_SOURCE[0]}"
while [ -L "$_self" ]; do _self="$(readlink -f "$_self")"; done
PLUGIN_DIR="$(cd "$(dirname "$_self")/.." && pwd)"
source "${PLUGIN_DIR}/lib/agent-paths.sh"
TOKEN_FILE="${GITHUB_TOKEN_FILE:-${AGENT_CONFIG_DIR}/github-token}"
META_FILE="${TOKEN_FILE}.meta"
ENV_RUNTIME_FILE="${GITHUB_APP_ENV_FILE:-${AGENT_CONFIG_DIR}/github-app-env}"

…then L97 can be deleted (already set above) and L122 becomes script_dir="${PLUGIN_DIR}/bin".

B — revert. In practice token-check.sh is only invoked by the hook at "$BIN_DIR/token-check.sh" using an absolute plugin path, so symlink safety isn't exercised. If that's the intended invariant, drop L36–38 back to the simple $(cd "$(dirname "$0")/.." && pwd) pattern and leave the whole file consistent. Reserve symlink-safe resolution for git-credential-github-app.sh, which is the one script genuinely expected to be invoked via symlink.

Either way: pick one. The current partial application is the worst of both worlds. Low priority vs. the README docs but worth resolving before un-drafting.

See also: related README thread.

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.

Partially addressed in da88564.

  • ✅ L97: PLUGIN_DIR now uses $_self (permalink).
  • ❌ L122: script_dir in do_generate_token() still uses dirname "$0" (permalink). Under a symlinked invocation, $script_dir/generate-token.sh on L130 will point at the symlink's directory, not the plugin's bin/. One-line fix:
  script_dir="$(cd "$(dirname "$_self")" && pwd)"

Or reuse the already-resolved $PLUGIN_DIR/bin/generate-token.sh and drop script_dir entirely.

Comment thread plugins/github-app/README.md
@jack-nsheaps jack-nsheaps Bot added the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 16, 2026
@henry-nsheaps henry-nsheaps Bot removed the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 16, 2026
henry-nsheaps[bot]

This comment was marked as outdated.

@jack-nsheaps jack-nsheaps Bot added the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 17, 2026
@henry-nsheaps henry-nsheaps Bot removed the request-review Request a one-time review from the Claude review bot (label is removed after review starts) label Apr 17, 2026
Copy link
Copy Markdown
Contributor

@henry-nsheaps henry-nsheaps Bot left a comment

Choose a reason for hiding this comment

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

👍 The README blocker from Henry's last review is fixed; two small $0 → symlink-safe consistency nits remain (non-blocking).

✅ README migration note + AGENT_NAME-in-parent-shell gotcha added (addresses discussion_r3093104531)
lib/agent-paths.sh double-source guard added (addresses discussion_r3093082520)
github-token-check.sh:25 now sources via ${CLAUDE_PLUGIN_ROOT} (addresses top of discussion_r3093080366)
bin/token-check.sh:97 now uses $_self (addresses top of discussion_r3093102509)
_UNKNOWN fallback, version bump 0.1.14 → 0.2.0, docs sweep verified
⚠️ bin/token-check.sh:122 still uses dirname "$0" — fix suggestion inline
⚠️ github-token-check.sh:95 still derives PLUGIN_DIR from $0 — fix suggestion inline

🖱️ Click to expand for full details

Scope

First review pass from claude-review on da88564a. Re-verified each of Henry's four still-open threads from the previous CHANGES_REQUESTED review by reading current file contents. The new commit addresses the README blocker and three of the four ⚠️ items; two lines still carry the old $0-based pattern (both flagged inline with suggestion blocks so they can be applied in one click).

Code Quality (88%, up from 85%)

The lib/agent-paths.sh + _AGENT_PATHS_LOADED guard now matches sibling lib/token-utils.sh:7-8 and the .claude/rules/shared-libs.md convention. ${CLAUDE_PLUGIN_ROOT} is used at the top of both hooks for the new helper. The two remaining $0 derivations (bin/token-check.sh:122, github-token-check.sh:95-98) are the only reason this isn't 100% — they're each a single-line change and have concrete suggestions attached.

Also, the _self / while [ -L ... ] / readlink -f block is now duplicated verbatim across three bin/ scripts. Per the previous recommended follow-ups, this would collapse to a single source line each if folded into agent-paths.sh via BASH_SOURCE[1]. Non-blocking.

Correctness / Potential Bugs

The README gotcha that was the primary blocker in the previous review is now handled in README.md:194-215: migration note for orphaned ~/.config/agent/ files and the AGENT_NAME-in-login-shell requirement for git-credential-github-app.sh. Both are the exact scenarios that would have silently re-introduced the cross-agent bug under a different path.

The one residual functional risk is the $0 at bin/token-check.sh:122: if a user symlinks token-check.sh (e.g. into ~/.local/bin), script_dir resolves to the symlink's directory and the generate-token.sh call at L130 fails with a confusing "No such file or directory". $_self is already resolved at L36–38, so reusing it fixes this in one line.

Security (95%)

Unchanged from Henry's read — strictly improved over 0.1.x. Per-agent isolation at ~/.agents/<name>/.config/, chmod 600 preserved on token/env/PEM. The 5% note is still the _UNKNOWN collision between unconfigured agents, now documented in the upgrade section.

Simplicity (85%)

Generally good. The shared helper + the fact that all seven consumer sites derive AGENT_CONFIG_DIR from one place is the right level of abstraction. The drag is the duplicated symlink-resolution block in three bin/ scripts (listed under follow-ups below). Moving it into agent-paths.sh collapses three 3-line blocks into three one-liners.

Convention / Style

  • camelCase settings keys (autoGitConfig, tokenFile) — compliant with .claude/rules/settings-key-naming.md.
  • agent-paths.sh now has the _AGENT_PATHS_LOADED guard per .claude/rules/shared-libs.md.
  • Docs consistently split <agent-name> placeholders (config examples) from ${AGENT_NAME} (runtime behaviour prose) — correct.

Test Coverage

PR test plan (4 manual items) is still unchecked. PR is still in draft, so that's expected. Recommend adding a fifth item from Henry's previous review: "git push via the credential helper picks up the per-agent token when AGENT_NAME is only in the login shell" — this directly exercises the newly-documented gotcha.

PR Description Accuracy

Body still matches the code changes, including the new files-changed list. No drift.

How I arrived at the scores

  • Code Quality 88% (up from 85%): 3 of 4 prior ⚠️s resolved; only the two $0 derivations remain.
  • Security 95% (unchanged): README upgrade note now covers the _UNKNOWN collision caveat.
  • Simplicity 85% (unchanged): duplicated resolver block is the only simplicity drag.
  • Confidence 90%: I re-read each file at da88564a, cross-checked Henry's threads 8/9/10/11 line-by-line, grepped for ~/.config/agent/ (only matches are in the intentional README migration example). 10% uncertainty is around whether the readlink -f / while [ -L ] pattern is intended as portable (it's GNU-only; stock macOS < 12.3 doesn't have readlink -f) — not a blocker for this PR.

Recommended follow-ups (non-blocking, can ship after):

  • Fold the _self / readlink -f resolver into agent-paths.sh (using BASH_SOURCE[1]) so each bin/ script needs only source "$(dirname "${BASH_SOURCE[0]}")/../lib/agent-paths.sh" — collapses three duplicated blocks, and fixes bin/token-check.sh:122 as a side-effect.
  • Promote the symlinked-script resolution pattern into shared/lib/ once settled — any plugin exposing scripts invoked outside the Claude harness (credential helpers, diff drivers, hook wrappers) will want the same primitive.
  • Log ${AGENT_NAME:-_UNKNOWN} in the hook_log "Authenticated as ..." line at github-token-init.sh:346 so the active per-agent directory is visible at session start.
  • Drop the dot prefix on ~/.agents/<name>/.config/ when ready (per @nsheaps's earlier note) — one-line change in agent-paths.sh now that it's centralized.
  • If macOS-bare support matters, switch readlink -f to a portable resolver (e.g., realpath with fallback, or a tiny shell function) — GNU readlink -f isn't in stock macOS < 12.3.

Notes:1234

Footnotes

  1. Workflow Run: https://github.com/nsheaps/ai-mktpl/actions/runs/24564118272/attempts/1

  2. PR: nsheaps/ai-mktpl#410

  3. Repo convention for shared libs: .claude/rules/shared-libs.md

  4. Repo convention for hook output: .claude/rules/hook-output-patterns.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant