feat(install): Claude bin launchers — absolute-path workers + per-account wrappers + guard - #51
Conversation
…t → qwen3.5-9b Tonight's hard-reboots on the M5 Max laptop were NOT RAM (swap 0) or claude-mem — they were too many concurrent local-GPU model engines: oMLX (cognee's 26B on :8000) + LM Studio reload-thrash + Cotypist + Grok (new today). A year of 8-16 cloud-API Claude terminals never crashed; heavy local MLX inference was the new variable. - lib/cognee.sh: default reviewer model 26B → qwen/qwen3.5-9b; comment explains LM Studio is the single local endpoint and why oMLX was retired. (Base default was already :1234; live plist updated + oMLX `brew services stop`'d separately.) - RESUME.md: corrected lockup root-cause narrative; documented the LM-Studio-only consolidation, the "one local engine" rule, and the offload-to-M3/DGX idea. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ripts Generated shell-aliases.sh used `command claude` (PATH-relative) and per-account shell *aliases* — both broke in the split-brain PATH: the tmux/launchd PATH differs from the interactive shell, and aliases vanish in non-interactive shells, scripts, and cron. - shell-aliases.sh now defines CLAUDE_BIN (default ~/.local/bin/claude, overridable); the bare-`claude` account guard runs "$CLAUDE_BIN". - claude-jason/titanium/semfreak are now standalone exec wrapper scripts in ~/.local/bin (work in scripts/cron/non-interactive), each pinning its CLAUDE_CONFIG_DIR and exec'ing the native binary by absolute path. - claude-use stays a function (it mutates the shell's env). Plan item A1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The guard re-prompted "Pick an account first" even after `claude-use jason` set CLAUDE_CONFIG_DIR — defeating the point of claude-use. Now: if an account is selected (CLAUDE_CONFIG_DIR set), bare `claude` launches it via $CLAUDE_BIN; only the unset (master ~/.claude) case still prompts. Subcommand/flag passthrough unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR updates operational documentation in RESUME.md with concrete current state and pending decisions across v3 and v4 development tracks; updates the default LLM model for Cognee code review from gemma to qwen3.5; and refactors the generated Claude account-routing dispatcher in lib/migrate.sh from shell aliases to a canonical CLAUDE_BIN variable with guard-based dispatch logic. ChangesOperational Updates & Claude Routing Refactor
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/migrate.sh (1)
277-299:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard only the bare interactive launch.
This allowlist blocks every unlisted Claude verb when
CLAUDE_CONFIG_DIRis unset, even though the surrounding comment says the interactive launch is the only guarded path. Any new or less-common subcommand will fail on the default account until this list is kept in sync.Suggested simplification
claude() { - # Pass-through commands/flags. Interactive REPL is the ONLY thing we guard. - case "${1:-}" in - update|doctor|migrate-installer|setup-token|mcp|config|ultrareview|""|"") ;; - esac if [[ $# -gt 0 ]]; then - case "$1" in - update|doctor|migrate-installer|setup-token|mcp|config|ultrareview \ - |--version|-v|--help|-h|-p|--print|--resume|--continue|-c|-r) - "$CLAUDE_BIN" "$@" - return $? - ;; - esac + "$CLAUDE_BIN" "$@" + return $? fi # An account is already selected for this shell (via claude-use) — just # launch it; no need to re-prompt. if [[ -n "${CLAUDE_CONFIG_DIR:-}" ]]; then🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/migrate.sh` around lines 277 - 299, The current allowlist in the initial case block prevents any unlisted Claude subcommand from running when CLAUDE_CONFIG_DIR is unset; instead, only the bare interactive launch should be guarded. Replace the allowlist logic in the block that checks $# with a simple unconditional forward: if [[ $# -gt 0 ]]; then "$CLAUDE_BIN" "$@"; return $?; fi so any provided args are passed through to CLAUDE_BIN; retain the subsequent CLAUDE_CONFIG_DIR check and the interactive prompt (claude-whoami, claude-use, claude-jason) as-is and update the comment to say “Guard only the bare interactive launch.”
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/migrate.sh`:
- Around line 312-318: The generated wrapper currently embeds
CLAUDE_CONFIG_DIR="$wcfg" which preserves a leading '~' instead of expanding it;
before writing the here-doc, expand tilde for wcfg (e.g. compute an expanded
variable like expanded_wcfg by replacing a leading ~ with $HOME or using eval
echo "$wcfg") and then write CLAUDE_CONFIG_DIR="$expanded_wcfg" into the
wrapper; update the code that generates the file (the loop using walias,
wprovider, wemail, wcfg and the here-doc that writes to "$bindir/$walias") to
reference the expanded variable instead of the raw wcfg.
---
Outside diff comments:
In `@lib/migrate.sh`:
- Around line 277-299: The current allowlist in the initial case block prevents
any unlisted Claude subcommand from running when CLAUDE_CONFIG_DIR is unset;
instead, only the bare interactive launch should be guarded. Replace the
allowlist logic in the block that checks $# with a simple unconditional forward:
if [[ $# -gt 0 ]]; then "$CLAUDE_BIN" "$@"; return $?; fi so any provided args
are passed through to CLAUDE_BIN; retain the subsequent CLAUDE_CONFIG_DIR check
and the interactive prompt (claude-whoami, claude-use, claude-jason) as-is and
update the comment to say “Guard only the bare interactive launch.”
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7b81c27e-7ad6-4ba9-acae-0b43be7e0c3f
📒 Files selected for processing (3)
RESUME.mdlib/cognee.shlib/migrate.sh
| while IFS=$'\t' read -r walias wprovider wemail wcfg wdesc; do | ||
| [[ "$wprovider" == "claude" ]] || continue | ||
| cat > "$bindir/$walias" <<WRAP | ||
| #!/usr/bin/env bash | ||
| # Auto-generated by subctl — per-account claude launcher. Re-run \`subctl install\`. | ||
| exec env CLAUDE_CONFIG_DIR="$wcfg" "\${CLAUDE_BIN:-\$HOME/.local/bin/claude}" "\$@" | ||
| WRAP |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
env CLAUDE_CONFIG_DIR="~/.claude-test" bash -lc 'printf "%s\n" "$CLAUDE_CONFIG_DIR"'Repository: webdevtodayjason/subctl
Length of output: 199
Expand ~ before embedding CLAUDE_CONFIG_DIR into the generated wrapper
In lib/migrate.sh, the wrapper sets CLAUDE_CONFIG_DIR="$wcfg" (quoted), so Bash won’t expand a leading ~. If wcfg comes from config as ~/.claude-*, the launcher will pass the literal ~ to claude.
Suggested fix
while IFS=$'\t' read -r walias wprovider wemail wcfg wdesc; do
[[ "$wprovider" == "claude" ]] || continue
+ wcfg="${wcfg/#\~/$HOME}"
cat > "$bindir/$walias" <<'WRAP'
#!/usr/bin/env bash
# Auto-generated by subctl — per-account claude launcher. Re-run `subctl install`.
exec env CLAUDE_CONFIG_DIR="$wcfg" "${CLAUDE_BIN:-$HOME/.local/bin/claude}" "$@"
WRAP📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| while IFS=$'\t' read -r walias wprovider wemail wcfg wdesc; do | |
| [[ "$wprovider" == "claude" ]] || continue | |
| cat > "$bindir/$walias" <<WRAP | |
| #!/usr/bin/env bash | |
| # Auto-generated by subctl — per-account claude launcher. Re-run \`subctl install\`. | |
| exec env CLAUDE_CONFIG_DIR="$wcfg" "\${CLAUDE_BIN:-\$HOME/.local/bin/claude}" "\$@" | |
| WRAP | |
| while IFS=$'\t' read -r walias wprovider wemail wcfg wdesc; do | |
| [[ "$wprovider" == "claude" ]] || continue | |
| wcfg="${wcfg/#\~/$HOME}" | |
| cat > "$bindir/$walias" <<'WRAP' | |
| #!/usr/bin/env bash | |
| # Auto-generated by subctl — per-account claude launcher. Re-run `subctl install`. | |
| exec env CLAUDE_CONFIG_DIR="$wcfg" "${CLAUDE_BIN:-$HOME/.local/bin/claude}" "$@" | |
| WRAP |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/migrate.sh` around lines 312 - 318, The generated wrapper currently
embeds CLAUDE_CONFIG_DIR="$wcfg" which preserves a leading '~' instead of
expanding it; before writing the here-doc, expand tilde for wcfg (e.g. compute
an expanded variable like expanded_wcfg by replacing a leading ~ with $HOME or
using eval echo "$wcfg") and then write CLAUDE_CONFIG_DIR="$expanded_wcfg" into
the wrapper; update the code that generates the file (the loop using walias,
wprovider, wemail, wcfg and the here-doc that writes to "$bindir/$walias") to
reference the expanded variable instead of the raw wcfg.
Reconciles
mainwith the A1/guard launcher work that's been deployed live since 2026-05-29 but never landed onmain. Clean superset oforigin/main(#49) — 3 commits, 0 behind, no rebase.What's in here (two co-committed stories — both already live, not split)
12fb595feat(install): launch Claude by absolute path (CLAUDE_BIN); per-account wrapper scripts (claude-jason/claude-titanium/claude-semfreak) in~/.local/bin.7651ffdfix(install): bareclaudelaunches theclaude-use-selected account (guard).2a52db5chore(local-ai): consolidate local models to LM Studio; cognee default → qwen3.5-9b.Touches only
lib/migrate.sh,lib/cognee.sh,RESUME.md.Safety
/Users/sempaths in the diff.-x --severity=error): clean on changed files; identicalcognee.shalready passed CI on chore(secrets): scrub hardcoded /Users/sem paths — green the no-secrets gate #49.Release
No VERSION bump in this PR (landing code ≠ cutting a release, per the one-story-per-tag discipline). Tag
v3.3.13after merge if desired.🤖 Generated with Claude Code
Summary by CodeRabbit
Documentation
Chores