Skip to content

feat(clawdbot): add remote mode for macOS nodes - #587

Merged
shunkakinoki merged 25 commits into
mainfrom
feat/clawdbot-remote-mode-nodes
Jan 17, 2026
Merged

feat(clawdbot): add remote mode for macOS nodes#587
shunkakinoki merged 25 commits into
mainfrom
feat/clawdbot-remote-mode-nodes

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Jan 16, 2026

Copy link
Copy Markdown
Owner

Summary

Configure Clawdbot for a hub-and-spoke architecture where Linux (kyber) runs the gateway and macOS machines connect as remote nodes.

Changes

  • Remote mode for macOS: macOS no longer runs a local gateway; instead connects to kyber via WebSocket (`ws://kyber.tail950b36.ts.net:18789`)
  • Bridge enabled on Linux: Gateway exposes bridge on LAN for node connectivity
  • Gateway token support: Extract `CLAWDBOT_GATEWAY_TOKEN` from `.env` for node authentication
  • Fish alias fix: `cliproxyapi` alias now works on both macOS and Linux

Summary by cubic

Add remote mode so macOS nodes connect to the kyber gateway over WebSocket. Kyber runs the gateway with a LAN bridge and token auth.

  • New Features

    • macOS runs in remote mode; connects to ws://kyber.tail950b36.ts.net:18789 (no local gateway/launchd).
    • Kyber gateway enabled via systemd with bridge bound to LAN for node connectivity.
    • Gateway token auth added; CLAWDBOT_GATEWAY_TOKEN supported and extracted to ~/.config/clawdbot/gateway-token.
    • Chromium set headless on kyber; browser stays GUI on macOS.
    • Dev ergonomics: add Zellij config, llm model config, and a cross-OS cliproxyapi function.
    • macOS: auto-install and launch Clawdbot.app on galactica; pin Clawdbot to v2026.1.16-2 and fix app packaging.
  • Migration

    • Add CLAWDBOT_GATEWAY_TOKEN to .env (copy from kyber: ~/.config/clawdbot/gateway-token).
    • Ensure Tailscale/MagicDNS resolves kyber.tail950b36.ts.net on macOS nodes.
    • Re-run secret extraction or home-manager to sync the token to each node.

Written for commit 218cd74. Summary will update on new commits.

Copilot AI review requested due to automatic review settings January 16, 2026 23:26
@mesa-dot-dev

mesa-dot-dev Bot commented Jan 16, 2026

Copy link
Copy Markdown

You do not have enough credits to review this pull request. Please purchase more credits to continue.

@coderabbitai

coderabbitai Bot commented Jan 16, 2026

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Zellij terminal multiplexer configuration with keybinds and plugin support
    • Added LLM configuration with OpenAI-compatible model support
    • Implemented multi-host gateway support with local gateway on Kyber and remote gateway on other nodes
    • Added automatic Clawdbot.app installation on macOS
  • Chores

    • Removed Claude install-skills functionality
    • Removed Kimi-k2 model profile
    • Enhanced environment variable configuration for gateway authentication

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds CLAWDBOT_GATEWAY_TOKEN and extraction; introduces host detection (isKyber/isGalactica/nodeName); makes Clawdbot use local gateway on Kyber and remote gateway with token on other hosts; propagates HOST in Makefile; converts a Fish alias to a function; adds Zellij config, LL M model entries, package reorderings, overlay clawdbot overrides, and removes install-skills.sh and its tests.

Changes

Cohort / File(s) Summary
Environment & Secrets
\.env.example, home-manager/modules/clawdbot/extract-secrets.sh
Adds CLAWDBOT_GATEWAY_TOKEN to .env.example and extracts it to "$CLAWDBOT_DIR/gateway-token"; preserves Telegram token extraction.
Clawdbot home-manager module
home-manager/modules/clawdbot/default.nix
Adds host import and remoteGatewayUrl; gates gateway/bridge/browser/providers with host.isKyber; configures local gateway for Kyber (mode=local) and remote gateway (mode=remote, url, tokenFile) for non-Kyber; adds installApp = false; adjusts launchd/systemd enablement and adds activation to inject remote token.
Host detection library
lib/host.nix
Adds isKyber, isGalactica, and nodeName derived from HOST/HOSTNAME with comments.
Makefile host propagation
Makefile
Prefixes activationPackage/nix-build/switch invocations with HOST=$(DETECTED_HOST) / HOST=$(HOST) so host context is passed during builds and switches.
Fish shell function
home-manager/programs/fish/default.nix, home-manager/programs/fish/functions/_cliproxyapi_function.fish
Replaces direct cliproxyapi alias with _cliproxyapi_function; function launches macOS binary on Darwin or restarts/tails systemd service on other OSes.
Zellij config & registration
config/zellij/config.kdl, config/zellij/default.nix, config/default.nix
Adds full Zellij config.kdl, Nix wrapper that symlinks it out-of-store, and registers ./zellij in public configs.
Home-manager packages list
home-manager/packages/default.nix
Reorders package list, adds llm, and introduces lib.optionals blocks for Linux and Linux non-CI conditional package groups.
Config & LLM assets
config/llm/default.nix, config/llm/extra-openai-models.yaml
Adds OS-specific out-of-store symlinks for extra-openai-models.yaml and four OpenAI-compatible model entries pointing to local cliproxyapi endpoint.
Codex / Claude cleanup
config/codex/config.toml, config/claude/default.nix, config/claude/install-skills.sh
Removes [profiles.kimi-k2] block; removes home.file.".claude/install-skills.sh" entry and deletes the install-skills.sh script and its spec.
Overlays & clawdbot override
overlays/default.nix
Replaces external clawdbot overlay with an inlined overlay that pins clawdbotVersion, overrides clawdbot-gateway/clawdbot-app, and exposes clawdbotPackages/tool override helpers.
nix-darwin activation
nix-darwin/default.nix
Adds a conditional post-activation script to install Clawdbot.app to /Applications on Galactica (non-CI).
Submodule update
dotagents
Advances tracked submodule commit.
Specs/tests
spec/coverage_spec.sh, spec/install_skills_spec.sh
Removes spec for install-skills.sh and updates coverage expectations accordingly.

Sequence Diagram

sequenceDiagram
    participant Client as Client (non-kyber / kyber)
    participant RemoteGW as Remote Gateway
    participant KyberHost as Kyber Host
    participant LocalGW as Local Gateway
    participant Browser as Chromium

    Client->>RemoteGW: HTTPS request (mode=remote) + CLAWDBOT_GATEWAY_TOKEN
    RemoteGW->>KyberHost: Forward request
    KyberHost->>LocalGW: Deliver to local gateway service
    LocalGW->>Browser: Spawn/headless Chromium to perform task
    Browser-->>LocalGW: Return result
    LocalGW-->>RemoteGW: Send response
    RemoteGW-->>Client: Return final response

    alt host.isKyber → mode=local
        Client->>LocalGW: Direct LAN request (mode=local)
        LocalGW->>Browser: Invoke Chromium locally
        Browser-->>LocalGW: Return result
        LocalGW-->>Client: Return response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I found a tiny gateway key,
Kyber hums and hops for me,
Mac copies apps with gentle paw,
Remote bridges stitch the law,
A rabbit’s hop — deployed with glee 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(clawdbot): add remote mode for macOS nodes' directly summarizes the main architectural change—configuring macOS nodes to connect remotely instead of running local gateways.
Description check ✅ Passed The description clearly explains the hub-and-spoke architecture, remote mode for macOS, gateway token support, and related improvements, all directly related to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/clawdbot-remote-mode-nodes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @shunkakinoki, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant architectural change to Clawdbot, shifting from a distributed gateway model to a centralized hub-and-spoke design. The primary goal is to enable macOS machines to operate as remote clients, connecting to a dedicated Linux gateway. This change simplifies deployment, centralizes resource management, and enhances the overall flexibility of the Clawdbot ecosystem by clearly defining roles for different operating systems.

Highlights

  • Remote Mode for macOS Clawdbot: macOS instances of Clawdbot no longer run a local gateway; instead, they connect as remote nodes to a central Linux gateway via WebSocket, streamlining the architecture.
  • Linux Gateway Bridge Enabled: The Linux gateway (kyber) is now configured to expose a bridge on the local area network (LAN), facilitating connectivity for remote Clawdbot nodes.
  • Gateway Token Authentication: A new CLAWDBOT_GATEWAY_TOKEN environment variable has been introduced and integrated into the secret extraction process, enabling authentication for remote nodes connecting to the gateway.
  • Cross-Platform Fish Alias Fix: The cliproxyapi Fish shell alias has been updated to correctly function on both macOS and Linux environments by incorporating OS-specific conditional logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@mesa-dot-dev

mesa-dot-dev Bot commented Jan 16, 2026

Copy link
Copy Markdown

Mesa Description

TL;DR

Configured Clawdbot for a hub-and-spoke architecture, enabling macOS nodes to connect as remotes to a Linux (kyber) gateway over WebSocket with token authentication, while also improving developer ergonomics with new Zellij, LLM, and cliproxyapi configurations.

What changed?

  • Clawdbot Configuration:
    • home-manager/modules/clawdbot/default.nix: Introduced host-specific Clawdbot configurations, enabling gateway on 'kyber' (Linux) and remote mode for macOS hosts (e.g., 'galactica'), connecting to 'kyber' via WebSocket with token injection.
    • .env.example: Added CLAWDBOT_GATEWAY_TOKEN for remote mode client authentication.
    • home-manager/modules/clawdbot/extract-secrets.sh: Updated to extract CLAWDBOT_GATEWAY_TOKEN.
    • overlays/default.nix: Replaced the default nix-clawdbot overlay with a custom one, pinning Clawdbot to a specific version and fixing the macOS app derivation.
    • nix-darwin/default.nix: Added conditional post-activation script for Clawdbot.app installation on 'galactica'.
  • Host Detection & Environment:
    • lib/host.nix: New file providing Nix functions (isKyber, isGalactica, nodeName) for host identification.
    • Makefile: Ensured consistent passing of the HOST environment variable to Nix commands.
  • cliproxyapi & Shell Experience:
    • home-manager/programs/fish/default.nix: Refactored cliproxyapi alias to use a new _cliproxyapi_function.
    • home-manager/programs/fish/functions/_cliproxyapi_function.fish: New function to manage the cliproxyapi service, with OS-specific logic.
    • config/codex/config.toml: Removed kimi-k2 profile and cliproxyapi setup.
  • LLM & Agent Configurations:
    • config/default.nix: Included new configuration paths for llm and zellij.
    • config/llm/default.nix: New NixOS module to configure extra-openai-models.yaml for the llm tool.
    • config/llm/extra-openai-models.yaml: New file defining custom OpenAI-compatible models (including cliproxyapi backend).
    • config/claude/default.nix & config/claude/install-skills.sh: Removed the Claude skill installation script and its configuration.
    • dotagents: Updated with new generated configuration and metadata for software agents.
  • Zellij Terminal Multiplexer:
    • config/zellij/default.nix: New NixOS module to set up Zellij configuration.
    • config/zellij/config.kdl: New comprehensive Zellij configuration file.
  • Package Management & Dependencies:
    • flake.lock: Updated Nix flake dependencies.
    • home-manager/packages/default.nix: Added go-task and llm packages.
  • Testing & Coverage:
    • spec/coverage_spec.sh: Removed coverage check for the deleted Claude skill installation script.
    • spec/install_skills_spec.sh: Deleted the test file for the Claude skill installation script.

Description generated by Mesa. Update settings

Copilot AI left a comment

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.

Pull request overview

This PR configures Clawdbot for a hub-and-spoke architecture where a Linux machine (kyber) runs the central gateway, and macOS machines connect to it as remote nodes via WebSocket.

Changes:

  • macOS nodes now connect to a remote gateway instead of running a local one
  • Linux gateway exposes a bridge on LAN for node connectivity and authentication
  • Gateway token extraction added to support node authentication
  • Fish shell alias updated to support both macOS and Linux platforms

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
home-manager/modules/clawdbot/default.nix Configures platform-specific gateway modes: local for Linux (kyber) with bridge enabled, remote for macOS nodes connecting via WebSocket
home-manager/modules/clawdbot/extract-secrets.sh Adds extraction of CLAWDBOT_GATEWAY_TOKEN from .env for node authentication
home-manager/programs/fish/default.nix Updates cliproxyapi alias to work on both macOS and Linux with platform-specific commands
.env.example Documents the new CLAWDBOT_GATEWAY_TOKEN environment variable

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread home-manager/programs/fish/default.nix Outdated
neofetch = "fastfetch";

cliproxyapi = "cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml";
cliproxyapi = "if test (uname) = Darwin; cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml; else; systemctl --user restart cliproxyapi && journalctl --user -u cliproxyapi -f; end";

Copilot AI Jan 16, 2026

Copy link

Choose a reason for hiding this comment

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

The Fish shell conditional syntax is incorrect. The if statement needs then before each command block, and should use ; correctly. The Darwin branch is missing the then keyword and uses ; incorrectly as a separator. The correct syntax should be: if test (uname) = Darwin; then cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml; else systemctl --user restart cliproxyapi && journalctl --user -u cliproxyapi -f; end

Suggested change
cliproxyapi = "if test (uname) = Darwin; cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml; else; systemctl --user restart cliproxyapi && journalctl --user -u cliproxyapi -f; end";
cliproxyapi = "if test (uname) = Darwin; then cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml; else systemctl --user restart cliproxyapi && journalctl --user -u cliproxyapi -f; end";

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +74
# - macOS: launchd disabled (remote mode - no local gateway)
launchd.enable = false; # macOS uses remote mode, no local gateway

Copilot AI Jan 16, 2026

Copy link

Choose a reason for hiding this comment

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

Setting launchd.enable = false unconditionally will disable the service on macOS even though the configuration includes macOS-specific remote mode settings (lines 98-109). Since macOS nodes need to run in remote mode to connect to the gateway, this should be conditional: launchd.enable = pkgs.stdenv.isDarwin; to ensure the service runs on macOS nodes.

Suggested change
# - macOS: launchd disabled (remote mode - no local gateway)
launchd.enable = false; # macOS uses remote mode, no local gateway
# - macOS: launchd runs the node in remote mode (no local gateway)
launchd.enable = pkgs.stdenv.isDarwin;

Copilot uses AI. Check for mistakes.
# Telegram token (Linux gateway only)
@grep@ -E "^CLAWDBOT_TELEGRAM_TOKEN=" "$DOTFILES_ENV" 2>/dev/null | @cut@ -d= -f2- | @tr@ -d '"' >"$CLAWDBOT_DIR/telegram-token" || true

# Gateway token (for remote mode clients / nodes)

Copilot AI Jan 16, 2026

Copy link

Choose a reason for hiding this comment

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

Comment uses 'clients / nodes' with inconsistent spacing. Should be 'clients/nodes' or 'remote mode nodes' for consistency.

Suggested change
# Gateway token (for remote mode clients / nodes)
# Gateway token (for remote mode clients/nodes)

Copilot uses AI. Check for mistakes.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces a remote mode for Clawdbot on macOS, allowing nodes to connect to a central Linux gateway. The changes are well-structured, particularly the refactoring of the Nix configuration to handle platform-specific settings for Linux and macOS. I have a few suggestions to improve maintainability and robustness. Specifically, I recommend making the remote gateway URL configurable, refactoring duplicated logic in a shell script into a helper function, and removing a hardcoded path from a fish alias to make it more portable across different Mac architectures.


# Remote gateway URL for non-kyber machines (macOS nodes connect here)
# Using Tailscale MagicDNS for direct connectivity (bridge is TCP, not HTTP)
remoteGatewayUrl = "ws://kyber.tail950b36.ts.net:18789";

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.

medium

The remoteGatewayUrl is hardcoded with a specific Tailscale MagicDNS name. This makes the configuration less flexible and harder to maintain if the URL changes or for use in different environments. Consider making this a configurable module option with the current value as its default. This would allow it to be easily overridden when needed.

Comment on lines +79 to 109
configOverrides =
# Linux (kyber): Local gateway mode with browser + bridge for nodes
lib.optionalAttrs pkgs.stdenv.isLinux {
gateway = {
mode = "local";
bind = "lan";
};
bridge = {
enabled = true;
bind = "lan"; # Allow nodes to connect from LAN/ingress
};
browser = {
enabled = true;
headless = true;
executablePath = "${pkgs.chromium}/bin/chromium";
noSandbox = true; # SUID sandbox requires root-owned binary with mode 4755
};
}
// lib.optionalAttrs pkgs.stdenv.isLinux {
executablePath = "${pkgs.chromium}/bin/chromium";
noSandbox = true; # SUID sandbox requires root-owned binary with mode 4755
};
}
// lib.optionalAttrs pkgs.stdenv.isLinux {
gateway = {
bind = "lan";
# macOS: Remote mode - connect to Linux gateway as a node
// lib.optionalAttrs pkgs.stdenv.isDarwin {
gateway = {
mode = "remote";
url = remoteGatewayUrl;
# Auth token read from file (set via extract-secrets or manually)
tokenFile = "${clawdbotDir}/gateway-token";
};
browser = {
enabled = true;
headless = false;
};
};

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.

medium

The refactoring of configOverrides is a great improvement for clarity and platform separation. However, there's some duplication in the browser configuration. Both the Linux and macOS blocks define browser.enabled = true;. You could pull this common setting out and define it once before the platform-specific lib.optionalAttrs blocks, then merge it with the platform-specific overrides. This would make the configuration slightly more DRY.

      configOverrides = {
        browser = {
          enabled = true;
        };
      } //
        # Linux (kyber): Local gateway mode with browser + bridge for nodes
        lib.optionalAttrs pkgs.stdenv.isLinux {
          gateway = {
            mode = "local";
            bind = "lan";
          };
          bridge = {
            enabled = true;
            bind = "lan"; # Allow nodes to connect from LAN/ingress
          };
          browser = {
            headless = true;
            executablePath = "${pkgs.chromium}/bin/chromium";
            noSandbox = true; # SUID sandbox requires root-owned binary with mode 4755
          };
        }
        # macOS: Remote mode - connect to Linux gateway as a node
        // lib.optionalAttrs pkgs.stdenv.isDarwin {
          gateway = {
            mode = "remote";
            url = remoteGatewayUrl;
            # Auth token read from file (set via extract-secrets or manually)
            tokenFile = "${clawdbotDir}/gateway-token";
          };
          browser = {
            headless = false;
          };
        };

Comment on lines +14 to +18
# Telegram token (Linux gateway only)
@grep@ -E "^CLAWDBOT_TELEGRAM_TOKEN=" "$DOTFILES_ENV" 2>/dev/null | @cut@ -d= -f2- | @tr@ -d '"' >"$CLAWDBOT_DIR/telegram-token" || true

# Gateway token (for remote mode clients / nodes)
@grep@ -E "^CLAWDBOT_GATEWAY_TOKEN=" "$DOTFILES_ENV" 2>/dev/null | @cut@ -d= -f2- | @tr@ -d '"' >"$CLAWDBOT_DIR/gateway-token" || true

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.

medium

The pipeline to extract secrets from the .env file is repeated here. This pattern is also used later in the script for CLAWDBOT_ANTHROPIC_KEY. To improve maintainability and reduce code duplication, you could define a helper function at the top of the script. For example:

extract_secret() {
  local key="$1"
  local outfile="$2"
  @grep@ -E "^${key}=" "$DOTFILES_ENV" 2>/dev/null | @cut@ -d= -f2- | @tr@ -d '"' >"$outfile" || true
}

Then you can replace the repeated pipelines with calls to this function.

Suggested change
# Telegram token (Linux gateway only)
@grep@ -E "^CLAWDBOT_TELEGRAM_TOKEN=" "$DOTFILES_ENV" 2>/dev/null | @cut@ -d= -f2- | @tr@ -d '"' >"$CLAWDBOT_DIR/telegram-token" || true
# Gateway token (for remote mode clients / nodes)
@grep@ -E "^CLAWDBOT_GATEWAY_TOKEN=" "$DOTFILES_ENV" 2>/dev/null | @cut@ -d= -f2- | @tr@ -d '"' >"$CLAWDBOT_DIR/gateway-token" || true
# Telegram token (Linux gateway only)
extract_secret "CLAWDBOT_TELEGRAM_TOKEN" "$CLAWDBOT_DIR/telegram-token"
# Gateway token (for remote mode clients / nodes)
extract_secret "CLAWDBOT_GATEWAY_TOKEN" "$CLAWDBOT_DIR/gateway-token"

Comment thread home-manager/programs/fish/default.nix Outdated
neofetch = "fastfetch";

cliproxyapi = "cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml";
cliproxyapi = "if test (uname) = Darwin; cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml; else; systemctl --user restart cliproxyapi && journalctl --user -u cliproxyapi -f; end";

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.

medium

The alias for cliproxyapi on Darwin uses a hardcoded path /opt/homebrew/bin/cliproxyapi. This is not robust as it's specific to Apple Silicon Macs. For Intel Macs, the Homebrew path is different (/usr/local/bin). Since your fish configuration adds the Homebrew bin directory to the PATH, you can rely on the shell to find the executable. Removing the hardcoded path will make the alias work correctly on any Mac architecture.

      cliproxyapi = "if test (uname) = Darwin; cd ~/.cli-proxy-api && cliproxyapi -config config.yaml; else; systemctl --user restart cliproxyapi && journalctl --user -u cliproxyapi -f; end";

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 4 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="home-manager/modules/clawdbot/default.nix">

<violation number="1" location="home-manager/modules/clawdbot/default.nix:74">
P1: macOS service disabled: launchd is set to false, leaving macOS nodes with no daemon to connect to the remote gateway, breaking remote mode unless started manually</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread home-manager/modules/clawdbot/default.nix Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.env.example:
- Around line 12-13: Reorder the two env entries so CLAWDBOT_GATEWAY_TOKEN
appears before CLAWDBOT_TELEGRAM_TOKEN to satisfy dotenv-linter; locate the
CLAWDBOT_GATEWAY_TOKEN and CLAWDBOT_TELEGRAM_TOKEN keys in the .env example,
move the CLAWDBOT_GATEWAY_TOKEN block (including its comment) above the
CLAWDBOT_TELEGRAM_TOKEN entry, and keep surrounding formatting and comments
intact.
🧹 Nitpick comments (3)
home-manager/programs/fish/default.nix (1)

55-56: Add a short note for the new platform-specific behavior.

The alias now restarts a service + tails logs on Linux but runs a local CLI on macOS. A brief comment here would make the behavior explicit for future edits.

📝 Suggested inline documentation
-      cliproxyapi = "if test (uname) = Darwin; cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml; else; systemctl --user restart cliproxyapi && journalctl --user -u cliproxyapi -f; end";
+      # Platform-aware: macOS runs local CLI; Linux restarts user service and tails logs
+      cliproxyapi = "if test (uname) = Darwin; cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml; else; systemctl --user restart cliproxyapi && journalctl --user -u cliproxyapi -f; end";

Based on learnings, document all major changes in Nix configurations.

home-manager/modules/clawdbot/default.nix (2)

12-15: Consider making remoteGatewayUrl a configurable option.

Hard-coding the gateway URL makes host overrides harder. Exposing it via mkOption (with a default) would keep per-host customization out of the module body.

As per coding guidelines, use mkOption for configurable options in Nix modules.


90-95: Expand the inline comment to better document headless mode as a security control and the gateway's threat model.

The existing comment correctly explains why the SUID sandbox is unavailable ("SUID sandbox requires root-owned binary with mode 4755"), which is a known Nix packaging constraint. However, the comment would be more helpful if it also noted that headless mode significantly reduces the attack surface compared to a GUI browser, and that this gateway runs in a private, controlled environment (accessed via Tailscale). Consider updating the comment to:

noSandbox = true; # SUID sandbox requires root-owned binary with mode 4755.
                  # Headless mode + private gateway mitigate sandbox absence.

This documents the architectural context—headless rendering eliminates JavaScript-in-GUI attacks, and the gateway is not exposed to arbitrary web content—making the risk assessment clearer to future maintainers.

Comment thread .env.example
Comment on lines +12 to +13
# Gateway token for remote mode clients (from kyber: cat ~/.config/clawdbot/gateway-token)
CLAWDBOT_GATEWAY_TOKEN=your-gateway-token-here

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Align key order with dotenv-linter.

The linter expects CLAWDBOT_GATEWAY_TOKEN before CLAWDBOT_TELEGRAM_TOKEN.

💡 Suggested reorder
-# Get Telegram bot token from `@BotFather`: https://t.me/BotFather
-CLAWDBOT_TELEGRAM_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
-# Get your Anthropic API key from: https://console.anthropic.com/
-CLAWDBOT_ANTHROPIC_KEY=sk-ant-api03-your-key-here
-# Gateway token for remote mode clients (from kyber: cat ~/.config/clawdbot/gateway-token)
-CLAWDBOT_GATEWAY_TOKEN=your-gateway-token-here
+# Gateway token for remote mode clients (from kyber: cat ~/.config/clawdbot/gateway-token)
+CLAWDBOT_GATEWAY_TOKEN=your-gateway-token-here
+# Get Telegram bot token from `@BotFather`: https://t.me/BotFather
+CLAWDBOT_TELEGRAM_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
+# Get your Anthropic API key from: https://console.anthropic.com/
+CLAWDBOT_ANTHROPIC_KEY=sk-ant-api03-your-key-here
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 13-13: [UnorderedKey] The CLAWDBOT_GATEWAY_TOKEN key should go before the CLAWDBOT_TELEGRAM_TOKEN key

(UnorderedKey)

🤖 Prompt for AI Agents
In @.env.example around lines 12 - 13, Reorder the two env entries so
CLAWDBOT_GATEWAY_TOKEN appears before CLAWDBOT_TELEGRAM_TOKEN to satisfy
dotenv-linter; locate the CLAWDBOT_GATEWAY_TOKEN and CLAWDBOT_TELEGRAM_TOKEN
keys in the .env example, move the CLAWDBOT_GATEWAY_TOKEN block (including its
comment) above the CLAWDBOT_TELEGRAM_TOKEN entry, and keep surrounding
formatting and comments intact.

@shunkakinoki
shunkakinoki force-pushed the feat/clawdbot-remote-mode-nodes branch from 2fcbd01 to ceb652d Compare January 17, 2026 14:11

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="home-manager/programs/fish/functions/_cliproxyapi_function.fish">

<violation number="1" location="home-manager/programs/fish/functions/_cliproxyapi_function.fish:3">
P2: Mac helper hard-codes `/opt/homebrew/bin/cliproxyapi`, so it fails on Intel macOS or other brew prefixes; should probe PATH or include `/usr/local/bin` fallback.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -0,0 +1,7 @@
function _cliproxyapi_function --description "Start cliproxyapi service"
if test (uname) = Darwin
cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml

@cubic-dev-ai cubic-dev-ai Bot Jan 17, 2026

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.

P2: Mac helper hard-codes /opt/homebrew/bin/cliproxyapi, so it fails on Intel macOS or other brew prefixes; should probe PATH or include /usr/local/bin fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/fish/functions/_cliproxyapi_function.fish, line 3:

<comment>Mac helper hard-codes `/opt/homebrew/bin/cliproxyapi`, so it fails on Intel macOS or other brew prefixes; should probe PATH or include `/usr/local/bin` fallback.</comment>

<file context>
@@ -0,0 +1,7 @@
+function _cliproxyapi_function --description "Start cliproxyapi service"
+    if test (uname) = Darwin
+        cd ~/.cli-proxy-api && /opt/homebrew/bin/cliproxyapi -config config.yaml
+    else
+        systemctl --user restart cliproxyapi && journalctl --user -u cliproxyapi -f
</file context>
Fix with Cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="config/llm/default.nix">

<violation number="1" location="config/llm/default.nix:4">
P1: home.file entries use unsupported `enable` option, causing Home‑Manager evaluation failure</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread config/llm/default.nix
{ config, pkgs, ... }:
{
home.file."Library/Application Support/io.datasette.llm/extra-openai-models.yaml" = {
enable = pkgs.stdenv.isDarwin;

@cubic-dev-ai cubic-dev-ai Bot Jan 17, 2026

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.

P1: home.file entries use unsupported enable option, causing Home‑Manager evaluation failure

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At config/llm/default.nix, line 4:

<comment>home.file entries use unsupported `enable` option, causing Home‑Manager evaluation failure</comment>

<file context>
@@ -1,6 +1,13 @@
+{ config, pkgs, ... }:
 {
+  home.file."Library/Application Support/io.datasette.llm/extra-openai-models.yaml" = {
+    enable = pkgs.stdenv.isDarwin;
+    source = config.lib.file.mkOutOfStoreSymlink ./extra-openai-models.yaml;
+    force = true;
</file context>
Fix with Cubic

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@config/llm/extra-openai-models.yaml`:
- Around line 4-22: Update the claude-haiku model entry to use the correct
Anthropic model identifier by replacing model_name value for the entry with
model_id "claude-haiku" to "claude-3-5-haiku-20241022"; then reorder the keys
for every model block (entries with model_id values "cliproxyapi",
"claude-sonnet", "claude-opus", and "claude-haiku") so they are alphabetically
sorted as api_base, api_key_name, model_id, model_name.
♻️ Duplicate comments (1)
config/llm/default.nix (1)

3-13: home.file does not support the enable option — use lib.mkIf for conditional file entries.

The enable attribute is not a valid option for home.file.<name> in home-manager. This will cause an evaluation failure. Use lib.mkIf to conditionally define each file entry instead.

🔧 Proposed fix using lib.mkIf
-{ config, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 {
-  home.file."Library/Application Support/io.datasette.llm/extra-openai-models.yaml" = {
-    enable = pkgs.stdenv.isDarwin;
+  home.file."Library/Application Support/io.datasette.llm/extra-openai-models.yaml" = lib.mkIf pkgs.stdenv.isDarwin {
     source = config.lib.file.mkOutOfStoreSymlink ./extra-openai-models.yaml;
     force = true;
   };
 
-  home.file.".config/io.datasette.llm/extra-openai-models.yaml" = {
-    enable = pkgs.stdenv.isLinux;
+  home.file.".config/io.datasette.llm/extra-openai-models.yaml" = lib.mkIf pkgs.stdenv.isLinux {
     source = config.lib.file.mkOutOfStoreSymlink ./extra-openai-models.yaml;
     force = true;
   };
 }
🧹 Nitpick comments (1)
lib/host.nix (1)

10-16: Consider reusing isKyber and isGalactica in nodeName to reduce duplication.

The nodeName attribute duplicates the hostname checks already defined in isKyber and isGalactica. Reusing these values improves maintainability.

♻️ Proposed refactor
+let
+  isKyber = builtins.getEnv "HOSTNAME" == "kyber" || builtins.getEnv "HOST" == "kyber";
+  isGalactica = builtins.getEnv "HOSTNAME" == "galactica" || builtins.getEnv "HOST" == "galactica";
+in
 {
-  # Detect if running on kyber (requires --impure flag, which Makefile already uses)
-  isKyber = builtins.getEnv "HOSTNAME" == "kyber" || builtins.getEnv "HOST" == "kyber";
-
-  # Detect if running on galactica (macOS node)
-  isGalactica = builtins.getEnv "HOSTNAME" == "galactica" || builtins.getEnv "HOST" == "galactica";
+  # Detect if running on kyber (requires --impure flag, which Makefile already uses)
+  inherit isKyber;
+
+  # Detect if running on galactica (macOS node)
+  inherit isGalactica;
 
   # Get the node name for clawdbot remote mode
   # Falls back to "unknown" if no hostname is detected
   nodeName =
-    if builtins.getEnv "HOSTNAME" == "kyber" || builtins.getEnv "HOST" == "kyber" then
-      "kyber"
-    else if builtins.getEnv "HOSTNAME" == "galactica" || builtins.getEnv "HOST" == "galactica" then
-      "galactica"
+    if isKyber then "kyber"
+    else if isGalactica then "galactica"
     else
       "unknown";
 }

Comment on lines +4 to +22
- model_id: cliproxyapi
model_name: claude-sonnet-4-20250514
api_base: http://localhost:8317/v1
api_key_name: cliproxyapi

- model_id: claude-sonnet
model_name: claude-sonnet-4-20250514
api_base: http://localhost:8317/v1
api_key_name: cliproxyapi

- model_id: claude-opus
model_name: claude-opus-4-20250514
api_base: http://localhost:8317/v1
api_key_name: cliproxyapi

- model_id: claude-haiku
model_name: claude-haiku-4-20250514
api_base: http://localhost:8317/v1
api_key_name: cliproxyapi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Anthropic Claude model identifiers 2026 latest available models

💡 Result:

Here are Anthropic’s latest Claude model identifiers (API model IDs) as of Jan 17, 2026:

  • Claude Opus 4.1 — claude-opus-4-1-20250805 (alias: claude-opus-4-1). [1]
  • Claude Opus 4 — claude-opus-4-20250514 (alias: claude-opus-4-0 / claude-opus-4). [1][2]
  • Claude Sonnet 4 — claude-sonnet-4-20250514 (alias: claude-sonnet-4-0 / claude-sonnet-4). [1][2]
  • Older/maintained models (examples): claude-3-7-sonnet-20250219 (alias: claude-3-7-sonnet-latest), claude-3-5-sonnet-20241022, claude-3-5-haiku-20241022. Use the Models list endpoint to see models available to your account. [1][2]

Recommendation: use the full snapshot ID (e.g., claude-opus-4-1-20250805) in production for stable behavior; aliases point to the latest snapshot. [1]

Sources: Anthropic docs (Models overview & API model examples). [1][2]


Fix invalid model identifier and sort configuration keys alphabetically.

The configuration has two issues:

  1. Invalid model identifier: claude-haiku-4-20250514 is not a valid Anthropic model. The current Claude Haiku model is claude-3-5-haiku-20241022. Update the haiku entry accordingly.

  2. Keys not alphabetically sorted: Per coding guidelines, configuration keys should be sorted alphabetically. Reorder each model entry to: api_base, api_key_name, model_id, model_name.

The model identifiers for Sonnet and Opus are correct: claude-sonnet-4-20250514 and claude-opus-4-20250514 are valid Anthropic models as of January 2026.

🤖 Prompt for AI Agents
In `@config/llm/extra-openai-models.yaml` around lines 4 - 22, Update the
claude-haiku model entry to use the correct Anthropic model identifier by
replacing model_name value for the entry with model_id "claude-haiku" to
"claude-3-5-haiku-20241022"; then reorder the keys for every model block
(entries with model_id values "cliproxyapi", "claude-sonnet", "claude-opus", and
"claude-haiku") so they are alphabetically sorted as api_base, api_key_name,
model_id, model_name.

@shunkakinoki
shunkakinoki enabled auto-merge (squash) January 17, 2026 17:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
spec/coverage_spec.sh (1)

96-116: Add spec assertions for newly listed cliproxyapi scripts.

Line 96 adds backup.sh, hydrate.sh, and wrapper.sh to the covered list, but the earlier “all required scripts have spec files” section doesn’t assert specs for them. That lets missing specs slip through, which contradicts the coverage intent. Please add corresponding spec checks (or remove them from the covered list if they’re exempt).

Proposed update (adjust spec filenames as appropriate)
+It 'has spec file for home-manager/services/cliproxyapi/scripts/backup.sh'
+The path "spec/cliproxyapi_backup_spec.sh" should be exist
+End
+
+It 'has spec file for home-manager/services/cliproxyapi/scripts/hydrate.sh'
+The path "spec/cliproxyapi_hydrate_spec.sh" should be exist
+End
+
+It 'has spec file for home-manager/services/cliproxyapi/scripts/wrapper.sh'
+The path "spec/cliproxyapi_wrapper_spec.sh" should be exist
+End
🤖 Fix all issues with AI agents
In `@home-manager/modules/clawdbot/default.nix`:
- Around line 65-79: The launchd job launchd.agents.clawdbot-app currently
hardcodes ProgramArguments to
"/Applications/Clawdbot.app/Contents/MacOS/Clawdbot" which will cause repeated
launch attempts if the app isn't present; change the job to either add a
StartInterval (e.g., short retry delay) and/or point ProgramArguments to a small
wrapper script (create a wrapper that checks for the app binary existence and
exits quietly if missing, then execs the real binary when present) and update
Label/ProgramArguments to call that wrapper so launchd doesn't repeatedly spawn
a failing binary.
♻️ Duplicate comments (1)
home-manager/modules/clawdbot/default.nix (1)

13-16: Hardcoded Tailscale URL reduces flexibility.

The remoteGatewayUrl is hardcoded with a specific Tailscale MagicDNS hostname. Consider making this a module option with this value as the default, allowing easier overrides for different environments or network configurations.

🧹 Nitpick comments (2)
overlays/default.nix (1)

37-65: Consider adding error handling for the find command edge case.

The find command with -quit is good for performance, but the error message could be more informative by listing what was found. Also, dontUnpack = true is set but src is already fetched via fetchzip, which auto-unpacks — this is correct but the combination might be slightly confusing.

♻️ Optional: More informative error message
              app_path="$(find "$src" -maxdepth 2 -name '*.app' -print -quit)"
              if [ -z "$app_path" ]; then
-               echo "Clawdbot.app not found in $src" >&2
+               echo "Clawdbot.app not found in $src. Contents:" >&2
+               ls -la "$src" >&2
                exit 1
              fi
home-manager/modules/clawdbot/default.nix (1)

48-63: Potential silent failure when config file doesn't exist initially.

The activation script checks for both TOKEN_FILE and CONFIG_FILE, but if CONFIG_FILE doesn't exist (e.g., first run before clawdbot creates it), the token injection silently skips. Consider logging when files are missing for debugging.

♻️ Optional: Add debug logging for missing files
      if [ -f "$TOKEN_FILE" ] && [ -f "$CONFIG_FILE" ]; then
        TOKEN=$(${pkgs.coreutils}/bin/cat "$TOKEN_FILE" | ${pkgs.coreutils}/bin/tr -d '\n')
        # Inject token into gateway.remote.token and remove tokenFile
        ${pkgs.jq}/bin/jq --arg token "$TOKEN" \
          '.gateway.remote.token = $token | del(.gateway.remote.tokenFile)' \
          "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && \
          ${pkgs.coreutils}/bin/mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
        echo "Injected gateway token into clawdbot config"
+     else
+       [ ! -f "$TOKEN_FILE" ] && echo "Skipping token injection: $TOKEN_FILE not found"
+       [ ! -f "$CONFIG_FILE" ] && echo "Skipping token injection: $CONFIG_FILE not found"
      fi

Comment on lines +65 to +79
# Auto-start Clawdbot.app on login (galactica only)
# App is installed to /Applications/Nix Apps/ via nix-darwin
launchd.agents.clawdbot-app = lib.mkIf (pkgs.stdenv.isDarwin && host.isGalactica) {
enable = true;
config = {
Label = "com.clawdbot.app";
ProgramArguments = [
"/Applications/Clawdbot.app/Contents/MacOS/Clawdbot"
];
RunAtLoad = true;
KeepAlive = false;
StandardOutPath = "/tmp/clawdbot-app.log";
StandardErrorPath = "/tmp/clawdbot-app.error.log";
};
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded app path assumes nix-darwin installation location.

The ProgramArguments uses /Applications/Clawdbot.app/... which depends on the nix-darwin post-activation script successfully copying the app. If that fails, launchd will repeatedly try to start a non-existent binary. Consider adding a StartInterval with a delay or a wrapper script that checks existence first.

🤖 Prompt for AI Agents
In `@home-manager/modules/clawdbot/default.nix` around lines 65 - 79, The launchd
job launchd.agents.clawdbot-app currently hardcodes ProgramArguments to
"/Applications/Clawdbot.app/Contents/MacOS/Clawdbot" which will cause repeated
launch attempts if the app isn't present; change the job to either add a
StartInterval (e.g., short retry delay) and/or point ProgramArguments to a small
wrapper script (create a wrapper that checks for the app binary existence and
exits quietly if missing, then execs the real binary when present) and update
Label/ProgramArguments to call that wrapper so launchd doesn't repeatedly spawn
a failing binary.

@shunkakinoki
shunkakinoki merged commit 7918ffe into main Jan 17, 2026
32 checks passed
@shunkakinoki
shunkakinoki deleted the feat/clawdbot-remote-mode-nodes branch January 17, 2026 18:29
@shunkakinoki
shunkakinoki restored the feat/clawdbot-remote-mode-nodes branch January 17, 2026 18:36
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