Skip to content

feat(agents): Conveniences for running Claude and support for OAuth based authentication#38

Merged
dpup merged 4 commits into
mainfrom
claude-runner
Jan 21, 2026
Merged

feat(agents): Conveniences for running Claude and support for OAuth based authentication#38
dpup merged 4 commits into
mainfrom
claude-runner

Conversation

@dpup
Copy link
Copy Markdown
Collaborator

@dpup dpup commented Jan 21, 2026

  • Add moat claude command for easy Claude Code execution in isolated containers
  • Support Claude Pro/Max subscriptions (OAuth) in addition to API keys
  • Add session management for Claude Code runs

Details

New moat claude command:

  # Start Claude Code interactively
  moat claude

  # Run with a prompt
  moat claude -p "fix the failing tests"

  # Add GitHub access
  moat claude --grant github

Credential support:

  • moat grant anthropic now imports OAuth credentials from Claude Code's macOS keychain
  • Falls back to API key if no OAuth credentials found
  • Credentials injected via proxy (never exposed to container)

Session tracking:

  • moat claude sessions lists Claude Code sessions
  • Sessions track workspace, state, and last accessed time
  • Syncs state with actual container status

Code improvements:

  • Extract shared execution logic into cmd/moat/cli/exec.go
  • Add provider setup abstraction for credential-specific container configuration
  • Add claude-code as installable dependency
  • Fix various lint issues (unused code, shadowed variables, preallocation)

dpup added 2 commits January 21, 2026 01:48
Adds a streamlined command for running Claude Code in isolated containers:

- `moat claude` - Start Claude Code interactively in current directory
- `moat claude -p "prompt"` - Run with a prompt (non-interactive)
- `moat claude --resume` - Resume a previous session
- `moat claude sessions` - List Claude sessions

Key features:
- Dual authentication: OAuth (Pro/Max subscription) or API key
- Session management for tracking and resuming conversations
- Automatic node@20 and git dependencies
- Shared execution flags with `moat run` (--rebuild, --keep, --env, etc.)

Architecture:
- New exec.go with shared ExecFlags, ExecOptions, and ExecuteRun()
- Refactored run.go to use shared execution logic
- New session.go for session persistence (~/.moat/claude/sessions/)
- Added git to dependency registry for Claude Code requirements
- Added UseOAuth config field for OAuth credential mounting
@dpup dpup requested a review from andybons January 21, 2026 02:00
@claude
Copy link
Copy Markdown

claude Bot commented Jan 21, 2026

PR Review: Claude Code Integration

This PR adds a comprehensive moat claude command with OAuth support and session management. Overall, this is a well-structured addition with good separation of concerns and excellent test coverage. Here's my detailed review:

✅ Strengths

Architecture & Design

  • Excellent abstraction: The ProviderSetup interface in internal/credential/provider.go is a clean, extensible design for handling provider-specific configuration
  • Good separation of concerns: exec.go properly extracts shared execution logic from the original run.go, reducing duplication
  • Smart credential detection: Auto-detecting Claude Code OAuth credentials from macOS keychain or filesystem is user-friendly
  • Proper cleanup handling: The cleanup path mechanism in ProviderSetup ensures temp directories are properly removed

Code Quality

  • Strong test coverage: All new packages have comprehensive unit tests with good edge case coverage
  • Security-conscious: OAuth tokens and API keys are handled separately with appropriate security measures
  • Error messages: Good, actionable error messages (e.g., in claude.go:106 suggesting to run claude first)
  • Documentation: Inline comments explain complex logic well (e.g., the proxy security model differences)

🔍 Issues Found

1. Race Condition in Session State Updates (Medium Priority)

Location: cmd/moat/cli/claude_sessions.go:63-89

The session state synchronization has a potential race condition:

for _, s := range sessions {
    r, err := runMgr.Get(s.RunID)
    // ... determine newState ...
    if s.State != newState {
        s.State = newState
        _ = sessionMgr.UpdateState(s.ID, newState)  // Error ignored
    }
}

Issues:

  • Errors from UpdateState are silently ignored (line 87)
  • Modifying s.State locally doesn't affect the session object returned to the user
  • No locking between reading and writing session state

Recommendation: At minimum, log the error. Consider whether state updates should be atomic or if eventual consistency is acceptable here.

2. Unused Variable Assignment (Low Priority)

Location: cmd/moat/cli/exec.go:169

lastSigTime = now
_ = lastSigTime // Mark as used for linter (value read in next iteration)

This is a workaround for a linter warning, but it's not ideal. The variable IS used in the next iteration. Recommendation: Remove the _ = lastSigTime line - the linter should recognize the loop usage.

3. Path Validation in Session Manager (Low Priority)

Location: internal/claude/session.go:184-186

The validSessionID regex validates session IDs but doesn't prevent directory traversal if someone crafts a malicious ID like ../../../etc. While filepath.Join helps, consider:

  • Using filepath.Clean on the result
  • Explicitly checking for path separators in the ID
  • Or using a whitelist-only approach (alphanumeric + hyphen only, which the regex does, but be defensive)

Current code:

if !validSessionID.MatchString(session.ID) {
    return fmt.Errorf("invalid session ID: %s", session.ID)
}
sessionDir := filepath.Join(m.dir, session.ID)

Recommendation: Add an extra safety check:

sessionDir := filepath.Join(m.dir, session.ID)
if !strings.HasPrefix(sessionDir, m.dir) {
    return fmt.Errorf("invalid session ID: directory traversal detected")
}

4. Potential OAuth Token Expiry Handling

Location: internal/credential/claude.go:44

The IsExpired() check exists but I don't see it being used anywhere in the flow. When credentials are loaded in claude_run.go:118, there's no check for expiration.

Recommendation: Consider adding expiry checking when loading credentials and warning users if their OAuth token is expired.

5. File Permissions on Credentials Directory (Security)

Location: internal/claude/provider.go:127

baseDir := filepath.Join(home, ".moat", "claude-creds")
if mkdirErr := os.MkdirAll(baseDir, 0700); mkdirErr != nil {

Good use of 0700 permissions! However, the temp subdirectory created by os.MkdirTemp may have different permissions depending on the system. Recommendation: Explicitly set permissions on the temp directory after creation to ensure 0700.

6. Missing Error Context

Location: cmd/moat/cli/claude_run.go:223-226

if sessionErr = sessionMgr.Create(absPath, r.ID, r.Name, grants); sessionErr != nil {
    log.Debug("failed to create session", "error", sessionErr)
}

Session creation failures are only logged at debug level. If sessions are an important feature, this should at least be a warning. Users might wonder why their sessions aren't showing up.

💡 Suggestions

1. Code Duplication in Tests

The mockProxyConfigurer is duplicated between internal/credential/provider_test.go and internal/claude/provider_test.go. Consider moving it to a shared test helper package or file.

2. Constants for Magic Strings

Location: internal/credential/provider.go:109

return len(token) > 10 && token[:10] == "sk-ant-oat"

Consider defining the OAuth token prefix as a constant for better maintainability:

const OAuthTokenPrefix = "sk-ant-oat"

func IsOAuthToken(token string) bool {
    return len(token) > len(OAuthTokenPrefix) && 
           strings.HasPrefix(token, OAuthTokenPrefix)
}

3. Time Format Consistency

Location: cmd/moat/cli/claude_sessions.go:148-175

The formatTimeAgo function is useful and could be shared across the codebase. Consider moving it to a internal/format or internal/util package if other commands might benefit from human-readable time formatting.

4. Documentation Enhancement

The README examples are excellent, but consider adding:

  • What happens when OAuth tokens expire (does moat handle refresh?)
  • Limitations of the OAuth credential import (e.g., tokens are session-bound)
  • Security implications of credential injection

5. Dependency Declaration

Location: internal/deps/registry.yaml

The claude-code dependency is added to the registry. Good! But consider documenting the version strategy - is this always latest, or should it be pinnable?

🎯 Performance Considerations

  1. Session Listing Performance: The List() function in session.go:118-146 reads all session files every time. This could be slow with many sessions. Consider:

    • Adding an LRU cache for session metadata
    • Lazy loading only when needed
    • Implementing pagination for moat claude sessions
  2. Concurrent Session State Updates: In claude_sessions.go, state updates happen sequentially in a loop. If there are many sessions, this could be slow. Consider using goroutines with a wait group for concurrent updates.

🔒 Security Review

Positive

  • Proper separation of OAuth vs API key handling ✅
  • Credentials never exposed to container (proxy injection) ✅
  • Read-only mounts where appropriate ✅
  • Secure file permissions (0600 for credentials, 0700 for directories) ✅
  • No credential leakage in logs ✅

Concerns

  • Credential file cleanup: While cleanup is implemented, if the process crashes, temp credential files in ~/.moat/claude-creds/run-* may remain. Consider adding a cleanup routine on startup to remove stale directories.
  • Mount validation: The mounts in provider.go are read-only where possible, which is good. However, ~/.claude.json is mounted read-write (line 81). Document why this is necessary.

📊 Test Coverage Assessment

Excellent test coverage overall:

  • ✅ Unit tests for all new credential logic
  • ✅ OAuth token parsing and validation
  • ✅ Provider setup configuration
  • ✅ Session management
  • ❓ Missing: Integration tests for the full moat claude flow (though this may be in e2e tests)

🎨 Code Style & Conventions

The code follows the project's conventions well:

  • ✅ Uses conventional commits format (based on PR title)
  • ✅ Good error messages with context
  • ✅ Follows Go idioms and formatting
  • ✅ Proper use of interfaces for abstraction

Minor: The comment style is mostly consistent, but some places use // comments where doc comments (// FunctionName ...) would be more appropriate for exported functions.

📝 Documentation

The documentation updates in README.md are clear and helpful. The examples tell a good story. One suggestion: add a troubleshooting section for common issues like:

  • OAuth credentials not found
  • Token expiration
  • Permission errors

Final Verdict

This is a high-quality PR with good architecture, solid test coverage, and thoughtful implementation. The issues found are mostly minor with a few medium-priority items around error handling and edge cases.

Recommended Actions Before Merge:

  1. High Priority: Fix or document the session state update error handling (issue feat(routing): add hostname-based service routing for agents #1)
  2. Medium Priority: Add OAuth token expiry checking (issue feat(proxy): Add HTTPS support to routing proxy #4)
  3. Medium Priority: Add stale credential directory cleanup
  4. Low Priority: Address the other minor issues as time permits

The PR is ready for merge after addressing the high-priority items. Great work! 🚀

@claude
Copy link
Copy Markdown

claude Bot commented Jan 21, 2026

Code Review: OAuth and Claude Code Support

This is high-quality work with excellent abstractions. Here are my findings:

Strengths:

  • Clean ProviderSetup interface with good extensibility
  • Comprehensive test coverage for new functionality
  • Good refactoring with exec.go reducing code duplication
  • Strong security practices (credential injection, file permissions)
  • Excellent user experience with session management

Critical Issues:

  1. Resource Cleanup (internal/claude/provider.go:108-116): Temporary credential directories may accumulate if cleanup fails. Need defer pattern to ensure cleanup.

  2. Shadowed Variable Bug (cmd/moat/cli/exec.go:169): Line 169 has suspicious linter workaround that suggests lastSigTime shadowing issue. This could break double-press detection.

  3. Silent Error Handling (cmd/moat/cli/claude_sessions.go:64-88): UpdateState errors are ignored, leading to stale UI state.

Medium Priority:

  1. OAuth Token Refresh: Code extracts RefreshToken but never uses it. Need to implement refresh or document limitation.

  2. Keychain Error Messages (internal/credential/claude.go:72-80): Should capture stderr for better error diagnostics.

  3. Dependency Checking (cmd/moat/cli/claude_run.go:258-263): hasDependency could incorrectly match node-sass@4 as node. Need more precise matching.

Minor:

  • Session states should use typed enum instead of string constants
  • CleanupOldSessions exists but is never called
  • Missing test coverage for CLI execution paths

Security: Good overall. Credentials properly injected, secure file permissions, directory traversal prevention.

Performance: Session listing scales linearly - fine for typical usage.

Overall: Approve with minor fixes recommended. Core functionality is solid. Main action items: fix shadowed variable bug and ensure cleanup reliability.

@claude
Copy link
Copy Markdown

claude Bot commented Jan 21, 2026

Code Review - PR #38: Claude Code Integration

This is an excellent addition to Moat! The implementation is well-structured, thoroughly tested, and follows the project's conventions. Here's my detailed review:

🎯 Overall Assessment

Strong Points:

  • ✅ Excellent code organization with clear separation of concerns
  • ✅ Good test coverage (unit tests for credential handling, provider setup, sessions)
  • ✅ Proper error handling with actionable error messages
  • ✅ Security-conscious design (credentials never exposed to containers)
  • ✅ Well-documented with helpful examples in README
  • ✅ Follows conventional commits format

📝 Code Quality

Architecture (Excellent)

The refactoring to extract shared execution logic into exec.go is well-done:

  • ExecFlags and ExecOptions provide clean abstractions
  • ExecuteRun() centralizes run lifecycle management
  • Provider setup pattern (ProviderSetup interface) is extensible and clean
  • Session management is properly isolated

Security (Good with minor note)

Credential Handling:

  • ✅ OAuth tokens properly extracted from macOS Keychain
  • ✅ Tokens injected via proxy (never in environment/filesystem visible to agent)
  • ✅ Different auth headers for OAuth vs API keys (Bearer vs x-api-key)
  • ✅ Proper use of anthropic-beta header for OAuth

Note: The OAuth token detection in internal/credential/provider.go:108-110:

func IsOAuthToken(token string) bool {
    return len(token) > 10 && token[:10] == "sk-ant-oat"
}

This is a reasonable heuristic but might be fragile if Anthropic changes token prefixes. Consider adding a comment about this being based on observed token format.

File Mounts (Potential Issue)

In internal/claude/provider.go:74-82, the code mounts ~/.claude.json with ReadOnly: false:

mounts = append(mounts, container.MountConfig{
    Source:   hostClaudeJSON,
    Target:   filepath.Join(containerHome, ".claude.json"),
    ReadOnly: false,  // ⚠️
})

Question: Is this intentional? Allowing the container to modify the host's ~/.claude.json file could be a security concern. Consider:

  1. If it's only for reading onboarding state, make it read-only
  2. If Claude Code must write to it, document why in a comment
  3. Consider copying it to a temp location and mounting that instead

Session Management (Good)

Strengths:

  • Proper state synchronization between sessions and runs (claude_sessions.go:63-89)
  • Clean JSON persistence
  • Input validation with regex for session IDs
  • Cleanup functionality for old sessions

Minor improvement: The session state sync in runSessionsList silently ignores errors (_ = sessionMgr.UpdateState(...)). Consider logging these failures at debug level.

Error Messages (Excellent)

Following the CLAUDE.md guidance perfectly. Examples:

"Claude Code credentials not found at %s\n" +
"  Have you logged into Claude Code? Run 'claude' to authenticate first"

Clear, actionable, tells users exactly what to do.

🐛 Potential Issues

1. Path Injection in Session ID (Low Risk)

internal/claude/session.go:175-178:

if !validSessionID.MatchString(id) {
    return fmt.Errorf("invalid session ID: %s", id)
}
sessionDir := filepath.Join(m.dir, id)

Good validation present! The regex prevents path traversal. ✅

2. Race Condition in Cleanup (Low Risk)

internal/claude/provider.go:112-115:

func (a *AnthropicSetup) Cleanup(cleanupPath string) {
    if cleanupPath != "" {
        _ = os.RemoveAll(cleanupPath)
    }
}

If cleanup fails, it's silently ignored. This is probably fine for temp directories, but consider logging at debug level.

3. Missing Validation (Low Risk)

cmd/moat/cli/claude_run.go:118-120:

if hasAnthropicCredential() {
    addGrant("anthropic")
}

hasAnthropicCredential() returns false on any error (key loading, store access). This is safe but might hide issues. Consider debug logging when it returns false due to errors vs simply no credential.

🚀 Performance Considerations

Mount Optimization

The code conditionally mounts host files only if they exist (good!):

if _, err := os.Stat(hostClaudeJSON); err == nil {
    mounts = append(mounts, ...)
}

This avoids unnecessary mounts. ✅

Session Listing

List() loads all session files and sorts them. For large numbers of sessions, this could be slow. Consider:

  • Caching or indexing if session count grows
  • Pagination for the CLI output
  • Background cleanup of old sessions

Not urgent - unlikely to have enough sessions for this to matter in practice.

✅ Test Coverage (Excellent)

Well-structured tests covering:

  • OAuth token expiration logic (claude_test.go)
  • Provider proxy configuration for both OAuth and API keys (provider_test.go)
  • Credential extraction from keychain and files
  • Mock implementations for testing

📚 Documentation

README Updates (Good)

  • Clear examples of the new moat claude command
  • Explains credential flow well
  • Shows expected output

Minor suggestion:

The README shows:

Found Claude Code credentials.
  Subscription: claude_pro
  Expires: 2025-02-15T10:30:00Z

Consider using a relative time format ("expires in 25 days") for better UX, matching the formatTimeAgo pattern used in sessions.

🔧 Recommendations

High Priority

1. Review ~/.claude.json mount permissions (provider.go:82)

  • Document why it needs write access, or make it read-only

Medium Priority

2. Add debug logging for credential detection

func hasAnthropicCredential() bool {
    key, err := credential.DefaultEncryptionKey()
    if err != nil {
        log.Debug("failed to get encryption key", "error", err)  // Add this
        return false
    }
    // ... rest of function
}

3. Add cleanup error logging

func (a *AnthropicSetup) Cleanup(cleanupPath string) {
    if cleanupPath != "" {
        if err := os.RemoveAll(cleanupPath); err != nil {
            log.Debug("failed to cleanup credentials dir", "path", cleanupPath, "error", err)
        }
    }
}

Low Priority (Nice to Have)

4. Add integration test for the full moat claude flow (if not already in e2e tests)

5. Consider adding session cleanup command: moat claude sessions clean --older-than 30d

6. Token refresh: The OAuth token handling doesn't include refresh logic. Document whether refresh is handled by Claude Code itself or if tokens are long-lived.

✨ Excellent Patterns to Highlight

  1. Provider abstraction - The ProviderSetup interface is well-designed and makes it easy to add new credential providers

  2. Callback pattern in ExecOptions.OnRunCreated - Clean way to inject session creation without coupling

  3. Dependency injection - The code ensures claude-code is installed via the dependency system rather than assuming it exists

  4. Separation of concerns - Credential extraction, proxy configuration, and session management are cleanly separated

🎬 Conclusion

This is production-ready code with just a few minor improvements to consider. The architecture is solid, tests are comprehensive, and it follows all the project conventions. Great work!

Approve with minor suggestions - the high-priority item about ~/.claude.json permissions should be reviewed, but it doesn't block merging if it's intentional.


Review generated with analysis of 22 files, 2655 additions, and focused testing of credential/session management

@dpup dpup merged commit a614ccf into main Jan 21, 2026
4 checks passed
dpup added a commit that referenced this pull request Jan 21, 2026
Plugin management (PR #35) followups:
- Fix command injection risk in runMarketplacesUpdate() by using
  MarketplacePath() for consistent validation with defense-in-depth
- Add cleanup helper functions (cleanupProxy, cleanupSSH, cleanupClaude)
  that log errors instead of discarding them
- Fix claudeGenerated cleanup lifecycle by storing temp dir path in
  Run struct and cleaning up in Stop/Wait/Destroy
- Add security tests for path traversal and URL parsing edge cases
- Add package-level documentation (doc.go)

Claude runner (PR #38) followups:
- Document ~/.claude.json mount security model (writable by design)
- Add comment about OAuth token prefix heuristic (sk-ant-oat)
- Add debug logging for credential detection failures
- Add debug logging for AnthropicSetup.Cleanup errors
- Add debug logging for session state sync failures
dpup added a commit that referenced this pull request Jan 21, 2026
Plugin management (PR #35) followups:
- Fix command injection risk in runMarketplacesUpdate() by using
  MarketplacePath() for consistent validation with defense-in-depth
- Add cleanup helper functions (cleanupProxy, cleanupSSH, cleanupClaude)
  that log errors instead of discarding them
- Fix claudeGenerated cleanup lifecycle by storing temp dir path in
  Run struct and cleaning up in Stop/Wait/Destroy
- Add security tests for path traversal and URL parsing edge cases
- Add package-level documentation (doc.go)

Claude runner (PR #38) followups:
- Document ~/.claude.json mount security model (writable by design)
- Add comment about OAuth token prefix heuristic (sk-ant-oat)
- Add debug logging for credential detection failures
- Add debug logging for AnthropicSetup.Cleanup errors
- Add debug logging for session state sync failures
dpup added a commit that referenced this pull request Jan 22, 2026
… as default (#42)

* feat(claude): Add `moat claude` command for easy Claude Code execution

Adds a streamlined command for running Claude Code in isolated containers:

- `moat claude` - Start Claude Code interactively in current directory
- `moat claude -p "prompt"` - Run with a prompt (non-interactive)
- `moat claude --resume` - Resume a previous session
- `moat claude sessions` - List Claude sessions

Key features:
- Dual authentication: OAuth (Pro/Max subscription) or API key
- Session management for tracking and resuming conversations
- Automatic node@20 and git dependencies
- Shared execution flags with `moat run` (--rebuild, --keep, --env, etc.)

Architecture:
- New exec.go with shared ExecFlags, ExecOptions, and ExecuteRun()
- Refactored run.go to use shared execution logic
- New session.go for session persistence (~/.moat/claude/sessions/)
- Added git to dependency registry for Claude Code requirements
- Added UseOAuth config field for OAuth credential mounting

* feat(agents): Conveniences for running Claude. Support non-API usage.

* fix(claude): Address code review feedback for plugin and runner PRs

Plugin management (PR #35) followups:
- Fix command injection risk in runMarketplacesUpdate() by using
  MarketplacePath() for consistent validation with defense-in-depth
- Add cleanup helper functions (cleanupProxy, cleanupSSH, cleanupClaude)
  that log errors instead of discarding them
- Fix claudeGenerated cleanup lifecycle by storing temp dir path in
  Run struct and cleaning up in Stop/Wait/Destroy
- Add security tests for path traversal and URL parsing edge cases
- Add package-level documentation (doc.go)

Claude runner (PR #38) followups:
- Document ~/.claude.json mount security model (writable by design)
- Add comment about OAuth token prefix heuristic (sk-ant-oat)
- Add debug logging for credential detection failures
- Add debug logging for AnthropicSetup.Cleanup errors
- Add debug logging for session state sync failures

* style(cli): Minor code polish

- Remove dead store and misleading linter workaround in exec.go
- Use strings.HasPrefix for clearer dependency check in hasDependency()

* fix(cli): Add input validation for better error handling

- Validate environment variable names match POSIX format
- Resolve symlinks in workspace paths for consistent behavior
- Add host validation in proxy credential methods (defense in depth)

These changes improve error messages for user mistakes and add
defense-in-depth validation for security-sensitive operations.

* feat(claude): Enable --dangerously-skip-permissions by default

Since Moat runs Claude Code in an isolated container, the container itself
serves as the security boundary. Permission prompts for file access and
command execution are redundant when the workspace is already sandboxed.

This adds --dangerously-skip-permissions to the claude command by default,
providing a smoother experience. Users who want manual approval for each
tool use can pass --noyolo to restore the default Claude behavior.

The rationale is that Moat's isolation model (container + credential proxy)
provides stronger security guarantees than per-operation prompts, while
the prompts add friction without meaningful additional protection.

* fix(run): Remove duplicate provider mounts code block

The provider-specific container mounts setup code was duplicated,
causing credentials to be mounted twice to the same target path
(e.g., /root/.claude/.credentials.json). This resulted in Docker
rejecting the container creation with 'Duplicate mount point' error.

* feat(deps): Run containers as non-root moatuser

Claude Code refuses --dangerously-skip-permissions when running as root.
This change creates a non-root user (moatuser, UID 1000) in generated
container images so Claude Code can run with full permissions.

Changes:
- Add moatuser creation to generated Dockerfiles (UID 1000)
- Install gosu for privilege dropping in entrypoint
- Update moat-init.sh to drop privileges after SSH setup
- Set USER directive for non-SSH containers
- Set WORKDIR to /home/moatuser

The moat-init entrypoint (used when SSH grants are present) runs as root
to create the SSH socket, then uses gosu to drop to moatuser before
executing the user's command.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* style: Fix gofmt formatting in runtime.go

* fix(security): Address code review feedback

- SSH socket permissions: Changed from mode 0777 to 0660 with proper
  ownership (chown to moatuser:moatuser) for tighter access control
- Marketplace names: Reject whitespace-only and control character names
  to prevent potential injection or filesystem issues
- Proxy logging: Add debug logging when invalid hosts are rejected in
  SetCredentialHeader and AddExtraHeader for easier debugging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix: Address code review feedback for PR #42

High priority fixes:
- SSH socket validation: Verify socat process is running and socket was
  created before proceeding, with warning messages on failure
- UID 1000 conflict: Delete existing user with UID 1000 before creating
  moatuser to avoid conflicts with base images

Medium priority fixes:
- hasDependency(): Add length check to ensure version exists after @
- ExtractHost(): Fix IPv6 literal parsing (e.g., git@[::1]:repo.git)
- MarketplacePath(): Allow "foo..bar" as valid filename, only reject
  ".." as complete path traversal (since / and \ are already rejected)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(claude): Always copy host .claude.json to container

Previously, there were two issues preventing .claude.json from being
available in the container:

1. CopyHostClaudeFiles was only called when there was an OAuth token
   (needsClaudeInit=true). Users with API keys or just plugins configured
   would not get their onboarding state copied.

2. The moat-init.sh script copied files to $HOME, which is /root when
   running as root. After dropping privileges to moatuser, Claude Code
   looks in /home/moatuser/.claude.json - which didn't exist.

Changes:
- Extract host file copying into separate CopyHostClaudeFiles function
- Call CopyHostClaudeFiles whenever a staging directory is created
- Also create staging dir when running Claude Code (ShouldSyncClaudeLogs)
- Fix moat-init.sh to copy files to /home/moatuser when running as root
  with moatuser available, instead of /root
- Ensure proper ownership of copied files

This ensures users don't have to re-complete onboarding in each container
session.

* feat(claude): Use CLAUDE_CODE_OAUTH_TOKEN for authentication

Switch from complex file-based OAuth credential injection to using
the CLAUDE_CODE_OAUTH_TOKEN environment variable. This is simpler
and more reliable.

Changes:
- `moat grant anthropic` now runs `claude setup-token` to obtain a
  long-lived OAuth token (falls back to legacy methods if unavailable)
- ContainerEnv now injects CLAUDE_CODE_OAUTH_TOKEN for OAuth tokens
- Remove need for complex staging directory credential file setup
- Update tests for new behavior

The env var approach eliminates issues with file permissions, path
remapping, and onboarding state not being preserved correctly.

* fix(security): Address code review feedback

- Fail with clear error instead of silently running as root when
  moatuser doesn't exist in container (moat-init.sh)
- Change container UID from 1000 to 5000 to avoid collision with
  existing users in base images (dockerfile.go, manager.go)
- Add regex validation for OAuth token extraction to reject
  malformed tokens (grant.go)
- Add comprehensive tests for UID mapping logic (manager_test.go)
- Add tests for malformed token rejection (grant_test.go)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix: Address additional code review feedback

- Use workspace owner UID/GID instead of process UID for container user
  mapping. This fixes permission issues when moat is run with sudo or
  as a different user than the workspace owner.
- Increase SSH socket creation timeout from 0.5s to 2s to handle slower
  socat startup
- Improve user deletion error handling in Dockerfile generation - log
  warnings instead of silently suppressing all errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@dpup dpup deleted the claude-runner branch January 29, 2026 04:37
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.

2 participants