fix: add macOS LaunchAgent setup for Claude Max subscription#10
fix: add macOS LaunchAgent setup for Claude Max subscription#10
Conversation
Fixes #6 - Claude Max subscription not working when Obsidian is launched from Dock/Spotlight because macOS GUI apps don't inherit shell environment variables. Changes: - Add LaunchAgent helper utility to create a login agent that automatically sets CLAUDE_CODE_OAUTH_TOKEN for GUI apps after each restart - Add "Setup Automatic Login" button in settings when token not detected - Show copyable manual command as alternative - Add CSS styling for setup UI components - Add unit tests for LaunchAgent helper functions The LaunchAgent sources both ~/.zshrc and ~/.bashrc to find the token, then uses launchctl setenv to make it available to GUI applications.
|
@claude review this |
|
Claude finished @Roasbeef's task in 1m 57s —— View job Code Review for PR #10: macOS LaunchAgent SetupI've completed a thorough review of the changes. Overall, this is a solid solution to the macOS GUI environment variable problem. The implementation is clean and well-tested. Here are my findings: ✅ Strengths
🔍 Security & Correctness Issues1. Shell Injection Vulnerability in LaunchAgent plist (High Priority)File: The plist command uses shell metacharacters without proper escaping: source ~/.zshrc 2>/dev/null || source ~/.bashrc 2>/dev/null; launchctl setenv CLAUDE_CODE_OAUTH_TOKEN "$CLAUDE_CODE_OAUTH_TOKEN"Problem: If a user's Why this matters: The token is sourced from the shell profile and then interpolated into a shell command. An attacker who can write to Mitigation: While this requires the attacker to already have write access to shell profiles (which is already game over), defense-in-depth suggests we should still prevent command injection. However, fixing this is non-trivial because we need the shell to expand Recommendation: Document this limitation in the code. Consider using source ~/.zshrc 2>/dev/null || source ~/.bashrc 2>/dev/null; printenv CLAUDE_CODE_OAUTH_TOKEN | xargs -0 launchctl setenv CLAUDE_CODE_OAUTH_TOKEN2. Manual Command Has Same Issue (Medium Priority)File: return 'launchctl setenv CLAUDE_CODE_OAUTH_TOKEN "$(echo $CLAUDE_CODE_OAUTH_TOKEN)"';Problem: If the user's Better approach: return 'launchctl setenv CLAUDE_CODE_OAUTH_TOKEN "$CLAUDE_CODE_OAUTH_TOKEN"';The 3. LaunchAgent Plist Should Support Both zsh and bash (Low Priority)File: Currently uses: Issue: If Better approach: Try both unconditionally { source ~/.zshrc 2>/dev/null; source ~/.bashrc 2>/dev/null; } && launchctl setenv CLAUDE_CODE_OAUTH_TOKEN "$CLAUDE_CODE_OAUTH_TOKEN"Or be explicit about which shell profile to use based on detection. 🐛 Code Quality Issues4. Error Handling Silently Ignores Important Failures (Medium Priority)File: try {
execSync(`launchctl load "${plistPath}"`, { stdio: "pipe" });
} catch {
// May fail if already loaded, that's ok.
}Problem: This catches ALL errors, not just "already loaded". If the plist has syntax errors or permission issues, this silently fails, and the user won't know setup didn't work. Recommendation: Be more specific about which errors are acceptable: try {
execSync(`launchctl load "${plistPath}"`, { stdio: "pipe" });
} catch (error) {
// Only ignore if already loaded, otherwise warn the user
const errorMsg = error instanceof Error ? error.message : String(error);
if (!errorMsg.includes('already loaded') && !errorMsg.includes('service already loaded')) {
console.warn('Failed to load LaunchAgent:', errorMsg);
// Still continue - it will work on next login
}
}Same issue exists in 5. File Permissions Could Be More Restrictive (Low Priority)File: fs.writeFileSync(plistPath, plistContent, { mode: 0o644 });Observation: Mode ✅ This is actually correct. No changes needed. 📝 Settings UI Review6. Good Progressive Disclosure (Positive)File: The collapsible 7. Button State Management (Minor Issue)File: setupBtn.disabled = true;
setupBtn.textContent = "Setting up...";
const result = await setupClaudeOAuthLaunchAgent();
new Notice(result.message);
this.display(); // Re-render.Issue: If Fix: Add try/catch or re-enable button in finally block: try {
setupBtn.disabled = true;
setupBtn.textContent = "Setting up...";
const result = await setupClaudeOAuthLaunchAgent();
new Notice(result.message);
} finally {
this.display(); // Re-render regardless
}🧪 Test Coverage Review8. Tests Are Too Shallow (Medium Priority)File: Issues:
Recommendations:
Example test to add: describe('setupClaudeOAuthLaunchAgent', () => {
it('should generate valid plist XML with correct command', async () => {
// Mock fs.writeFileSync to capture the plist content
// Parse XML and verify structure
// Ensure command includes proper shell sourcing
});
});📚 Documentation Review9. Excellent Planning Document (Positive)File: This is a great example of thorough problem analysis and solution design. Well done! 10. Missing User-Facing Documentation (Low Priority)Consider adding:
🎨 CSS ReviewFile: ✅ CSS looks good. Properly scoped class names, uses Obsidian's CSS variables for theming. No issues found. 📊 Summary
Recommended Actions Before MergeMust Fix:
Should Fix: Nice to Have: Overall AssessmentVerdict: ✅ Approve with minor changes This PR solves a real problem with the correct macOS-native approach. The code is well-structured and mostly well-tested. The security concerns are minimal (mostly edge cases), and the UX is thoughtful. The main areas for improvement are:
Great work on this feature! 🎉 |
Summary
Fixes #6 - Claude Max subscription not working when Obsidian is launched from Dock/Spotlight.
macOS GUI apps don't inherit shell environment variables. When users run
claude setup-token, it addsCLAUDE_CODE_OAUTH_TOKENto their shell profile (~/.zshrc or ~/.bashrc), but Obsidian launched from the Dock doesn't see it.Solution
Add a one-click "Setup Automatic Login" button that creates a macOS LaunchAgent. This agent runs at every login and uses
launchctl setenvto make the token available to GUI applications.Changes
src/utils/launchAgentHelper.ts- LaunchAgent management functions (setup, remove, detect)src/settings/SettingsTab.ts- Claude Max setup UI with automatic setup button and manual command fallbackHow it works
claude setup-token(adds token to shell profile)~/Library/LaunchAgents/com.anthropic.claude-oauth.plistsource ~/.zshrc; launchctl setenv CLAUDE_CODE_OAUTH_TOKEN ...Test Plan