Claude Skill

add-signal

Add Signal channel integration via signal-cli device-link. Native adapter — no Chat SDK bridge.

LLM Mart · 0 points · 14 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download nanocoai-nanoclaw-.claude_skills_add-signal-ad8837c.zip · 6 KB
Part of nanocoai/nanoclaw — 49 skills

Install

skills CLI npx skills add https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/add-signal
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install nanocoai-nanoclaw@llmmart
Git git clone https://github.com/nanocoai/nanoclaw.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole nanocoai/nanoclaw collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Add Signal Channel

Adds Signal support via a native adapter that speaks JSON-RPC to a signal-cli daemon — no Chat SDK bridge, only Node.js builtins. NanoClaw links to Signal as a secondary device on your existing phone: no new number, no bot API. Your assistant sends and receives as the number on the phone that scans the link.

Apply

1. Install signal-cli

NanoClaw talks to Signal through signal-cli, which has no bot API of its own. Install it if it isn't on PATH yet — Homebrew on macOS, the native release binary on Linux (neither needs Java). If it's already installed this is a no-op:

command -v signal-cli >/dev/null 2>&1 || bash setup/install-signal-cli.sh

2. Copy the adapter and its registration test

Fetch the channels branch and copy the Signal adapter and its registration test into src/channels/ (overwrite — the branch is canonical):

src/channels/signal.ts
src/channels/signal-registration.test.ts

3. Register the adapter

Append the self-registration import to the channel barrel (skipped if the line is already present). This one line is the skill's only reach-in into core:

import './signal.js';

4. Install the QR-rendering dependency

The device-link step renders the linking URL as a terminal QR via qrcode. Pinned to exact versions — the supply-chain policy rejects ranges and latest:

qrcode@1.5.4
@types/qrcode@1.5.6

The adapter itself consumes only Node.js builtins, so there is no adapter package to install — qrcode is purely for rendering the link during setup.

5. Build and validate

Build first: it guards the adapter's typed core-API consumption. Then run the one integration test.

pnpm run build
pnpm exec vitest run src/channels/signal-registration.test.ts

signal-registration.test.ts imports the real channel barrel and asserts the registry contains signal. It goes red if the import './signal.js'; line is deleted or drifts, or if the barrel fails to evaluate — so the channel genuinely would not register. The adapter has no npm dependency to guard; its typed core-API consumption is covered by the build. End-to-end delivery against a real Signal account is verified manually once the service runs.

Link your Signal account

This is the whole credential step. signal-cli opens a device-link handshake, prints a sgnl://linkdevice… URL, and renders it as a scannable QR. You scan it once from the phone that already runs Signal; that phone's number becomes the account NanoClaw sends and receives as — no number is registered.

The device-link runs signal-cli, so it must be reachable first — on PATH, or at $SIGNAL_CLI_PATH. If step 1's install didn't land, the link step has nothing to drive; confirm it's present before linking (re-run step 1 if this fails):

command -v signal-cli >/dev/null 2>&1 || [ -x "$SIGNAL_CLI_PATH" ]

Tell the user:

Link NanoClaw to your Signal account:
1. On the phone that runs Signal, open Signal → Settings → Linked Devices → Link New Device.
2. Scan the QR code shown below — or open the `sgnl://linkdevice…` link printed under it on that phone.
3. Wait for confirmation. The linking URL expires after ~3 minutes; re-run this step for a fresh one.

Run the device-link. It blocks until you scan, then reports the linked phone number back as the account — that number is both your owner handle and the conversation address the wiring step needs:

pnpm exec tsx setup/index.ts --step signal-auth

owner_handle and platform_id both come back as the bare phone number (e.g. +15551234567). Your assistant reaches you through Signal's Note to Self, so the owner conversation is addressed by your own number — not a per-contact UUID.

Persist the account

Store the linked number so the adapter binds the right account on start, then sync it into the container env:

SIGNAL_ACCOUNT={{platform_id}}

Restart

Restart the service so it loads the Signal adapter and binds the account you just linked, and wait for its CLI socket before wiring:

bash setup/lib/restart.sh

Wiring

DMs

After the service starts, send any message to the Signal number from your personal Signal app. The router auto-creates a messaging_groups row. Then:

pnpm exec tsx scripts/q.ts data/v2.db \
  "SELECT id, platform_id FROM messaging_groups WHERE channel_type='signal' ORDER BY created_at DESC LIMIT 5"

Pass the id to /init-first-agent or /manage-channels to wire it to an agent group.

Groups

Add the Signal number to a group from your phone, send any message, then wire the resulting row the same way. Each group gets its own session with the default shared mode (one session per agent + messaging group). Create the wiring with ncl — the host service must be running (ncl connects to it over a Unix socket):

# Engage mode/pattern default to the Signal adapter's declared channel defaults
ncl wirings create --messaging-group-id mg-GROUPID --agent-group-id ag-AGENTID

Grant user access

New Signal users (including the owner's Signal identity) are silently dropped with not_member until granted access. After the user's first message appears in messaging_groups (host service running):

ncl users create --id "signal:UUID" --kind signal --display-name "<name>"
ncl roles grant --user "signal:UUID" --role owner
ncl members add --user "signal:UUID" --group ag-AGENTID

Find the UUID from messaging_groups.platform_id or the users table.

Next Steps

If you're in the middle of /setup, return to the setup flow now. Otherwise wire this channel with /init-first-agent (or /manage-channels).

Channel Info

  • type: signal
  • terminology: Signal has "chats" (1:1 DMs) and "groups." The owner reaches their own assistant through Note to Self.
  • platform-id-format:
    • Owner DM (Note to Self): the bare phone number +<number> (e.g. +15551234567) — your own messages route back as inbound with isFromMe, addressed by your number.
    • Third-party DM: signal:{UUID} — the sender's Signal ACI, not their phone number.
    • Group: signal:{base64GroupId} — base64-encoded GroupV2 ID.
  • how-to-find-id: The owner number comes back from the device-link step above. For third parties or groups, send a message to the bot, then query messaging_groups.
  • supports-threads: no
  • typical-use: Personal assistant via Signal DMs or small group chats
  • default-isolation: One agent per Signal account. Multiple chats with the same operator can share an agent group; groups with other people should typically get their own agent group (the default shared session mode already gives each messaging group its own session).

Features

  • Markdown formatting — **bold**, *italic* / _italic_, `code`, ```code fence```, ~~strike~~, ||spoiler|| (converted to Signal's offset-based text styles).
  • Quoted replies — replyTo* fields populated from Signal quotes.
  • Typing indicators — DMs only (Signal doesn't support group typing).
  • Note to Self — messages you send to your own account from another device route to the agent as inbound with isFromMe: true.
  • Voice attachments — detected but not transcribed by default; the agent receives a [Voice Message] placeholder. Run /add-voice-transcription for local transcription.

Not supported yet: outbound file attachments (logged and dropped), edit/delete messages, reactions.

Alternatives

Register a dedicated number instead of linking

The device-link above joins Signal as a secondary device on an existing number. If you'd rather give the assistant its own number, register a dedicated SIM or VoIP number that NanoClaw owns entirely. This path takes a captcha, an SMS (or voice) verification, and an optional profile name.

VoIP numbers: Signal requires SMS verification before voice. Some VoIP providers are blocked even for voice calls. If registration fails with an auth error, try a different provider or a physical SIM.

Step 1: Solve the CAPTCHA

Signal requires a CAPTCHA on first registration:

  1. Open https://signalcaptchas.org/registration/generate.html in a browser
  2. Solve the captcha
  3. Right-click the "Open Signal" button → Copy Link
  4. The link starts with signalcaptcha:// — the token is everything after that prefix

Step 2: Request SMS verification

signal-cli -a +1YOURNUMBER register --captcha "PASTE_TOKEN_HERE"

Step 3: Voice call fallback (if your number can't receive SMS)

Wait ~60 seconds after the SMS request, then:

signal-cli -a +1YOURNUMBER register --voice --captcha "SAME_TOKEN"

Signal calls your number and reads a 6-digit code. The same captcha token is reusable — no need to solve a new one.

You must request SMS first. Requesting voice immediately fails with Invalid verification method: Before requesting voice verification…

Step 4: Verify

signal-cli -a +1YOURNUMBER verify CODE

No output = success.

Step 5: Set profile name (optional)

⚠ Stop NanoClaw before running signal-cli commands — the daemon holds an exclusive lock on its data directory while running.

Run from your NanoClaw project root:

source setup/lib/install-slug.sh

# macOS
launchctl unload ~/Library/LaunchAgents/$(launchd_label).plist
signal-cli -a +1YOURNUMBER updateProfile --name "YourBotName"
# optionally: --avatar /path/to/avatar.jpg
launchctl load ~/Library/LaunchAgents/$(launchd_label).plist

# Linux
systemctl --user stop $(systemd_unit)
signal-cli -a +1YOURNUMBER updateProfile --name "YourBotName"
systemctl --user start $(systemd_unit)

Once registered, set SIGNAL_ACCOUNT to this number (as under Persist the account above) and restart the service.

Optional configuration

These .env keys tune how NanoClaw talks to the signal-cli daemon. All are optional — the defaults work for the device-link flow above.

# TCP daemon host and port (default: 127.0.0.1:7583)
SIGNAL_TCP_HOST=127.0.0.1
SIGNAL_TCP_PORT=7583

# Path to the signal-cli binary (default: resolved on PATH)
SIGNAL_CLI_PATH=/usr/local/bin/signal-cli

# Whether NanoClaw manages the daemon lifecycle (default: true).
# Set to false if you run signal-cli daemon externally.
SIGNAL_MANAGE_DAEMON=true

# signal-cli data directory (default: ~/.local/share/signal-cli)
SIGNAL_DATA_DIR=~/.local/share/signal-cli

Security note: keep the TCP host on 127.0.0.1. The daemon has no auth — binding it to a public interface would expose your full Signal account to the network.

Troubleshooting

Daemon not reachable

grep "Signal" logs/nanoclaw.log | tail

If you see Signal daemon failed to start. Is signal-cli installed and your account linked?:

  • Confirm signal-cli is on PATH (or set SIGNAL_CLI_PATH)
  • Confirm the account is linked: signal-cli -a +YOURNUMBER listIdentities should succeed without prompting

If you see Signal daemon not reachable at 127.0.0.1:7583 and SIGNAL_MANAGE_DAEMON=false, start the daemon yourself: signal-cli -a +YOURNUMBER daemon --tcp 127.0.0.1:7583.

Bot not responding

  1. Channel initialized: grep "Signal channel connected" logs/nanoclaw.log | tail -1
  2. Channel wired: pnpm exec tsx scripts/q.ts data/v2.db "SELECT mg.platform_id, mg.name FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id = mga.messaging_group_id WHERE mg.channel_type='signal'"
  3. Service running: launchctl print gui/$(id -u)/"$(. setup/lib/install-slug.sh && launchd_label)" (macOS) / systemctl --user status "$(. setup/lib/install-slug.sh && systemd_unit)" (Linux)
  4. Check for duplicate service instances — if logs/nanoclaw.error.log shows No adapter for channel type channelType="signal" despite the adapter starting, two NanoClaw processes are racing. See the /debug skill section "No adapter for channel type / Messages silently lost" for the full fix.

Messages delivered but never arrive (null platformMsgId)

Signal responses show platformMsgId=undefined in the main log. This means the delivery poll ran but found no adapter — likely a duplicate service instance issue (see above). Affected messages cannot be retried; the user must resend.

Lost connection mid-session

If you see Signal channel lost TCP connection to signal-cli daemon in the logs, the daemon dropped the connection. Restart the service to re-establish.

Messages dropped with not_member

The Signal user hasn't been granted membership. New Signal senders — including the owner's Signal identity — are gated until granted access. /init-first-agent grants the owner automatically; for other users, grant access as shown under Grant user access in the Wiring section (or via /manage-channels) after their first message appears in messaging_groups. This affects every new Signal user, since their Signal identity is a separate user record from their identity on other channels even if it's the same person.

Captcha required

Signal requires a captcha for new registrations. Go to https://signalcaptchas.org/registration/generate.html, solve it, right-click "Open Signal", copy the link, extract the token after signalcaptcha://.

Invalid verification method: Before requesting voice verification…

You must request SMS first, wait ~60 seconds, then request voice. Both steps can use the same captcha token.

Config file in use / daemon lock

signal-cli holds an exclusive lock on its data directory while the daemon is running. Stop NanoClaw before running any signal-cli commands directly, then restart afterward.

Group replies going to DM instead of group

Modern Signal groups use GroupV2. The adapter must extract the group ID from envelope?.dataMessage?.groupV2?.id — not groupInfo?.groupId, which is GroupV1/legacy. If group messages are routing as DMs, check src/channels/signal.ts and confirm the groupId extraction falls through to groupV2.id.

QR / linking URL expired

The sgnl://linkdevice… URL (and the Path A registration captcha) expire after a few minutes. Re-run the device-link step to get a fresh QR.

Files (nanoclaw)
  • apply-fixtures.json 370 B
    {
      "notes": "Conformance fixtures for scripts/skill-conformance.test.ts — no prompts in this skill; the fixture exists to feed the effect:step device-link's terminal-block fields (capture:platform_id=ACCOUNT,owner_handle=ACCOUNT).",
      "scenarios": [
        {
          "name": "device-link",
          "inputs": {},
          "stepFields": { "ACCOUNT": "+15550001111" }
        }
      ]
    }
    
  • REMOVE.md 1 KB
    # Remove Signal
    
    Every step is idempotent — safe to re-run.
    
    ## 1. Remove the adapter
    
    Delete the self-registration import from `src/channels/index.ts` (skip if already gone):
    
    ```typescript
    import './signal.js';
    ```
    
    Then delete the copied adapter and its tests:
    
    ```bash
    rm -f src/channels/signal.ts src/channels/signal-registration.test.ts src/channels/signal.test.ts
    ```
    
    ## 2. Remove credentials
    
    Remove the `SIGNAL_*` lines from `.env`:
    
    ```bash
    SIGNAL_ACCOUNT
    SIGNAL_TCP_HOST
    SIGNAL_TCP_PORT
    SIGNAL_CLI_PATH
    SIGNAL_MANAGE_DAEMON
    SIGNAL_DATA_DIR
    ```
    
    ## 3. Rebuild and restart
    
    Run from your NanoClaw project root:
    
    ```bash
    pnpm run build
    source setup/lib/install-slug.sh
    
    # Linux
    systemctl --user restart $(systemd_unit)
    
    # macOS
    launchctl kickstart -k gui/$(id -u)/$(launchd_label)
    ```
    
    ## 4. Unlink the Signal account (optional)
    
    To unlink NanoClaw's device from the Signal account:
    
    ```bash
    signal-cli -a +1YOURNUMBER removeDevice --deviceId <id>
    ```
    
    Find the device id with `signal-cli -a +1YOURNUMBER listDevices`.
    
  • SKILL.md 14.3 KB
    ---
    name: add-signal
    description: Add Signal channel integration via signal-cli device-link. Native adapter — no Chat SDK bridge.
    ---
    
    # Add Signal Channel
    
    Adds Signal support via a native adapter that speaks JSON-RPC to a
    [signal-cli](https://github.com/AsamK/signal-cli) daemon — no Chat SDK bridge,
    only Node.js builtins. NanoClaw links to Signal as a *secondary device* on your
    existing phone: no new number, no bot API. Your assistant sends and receives as
    the number on the phone that scans the link.
    
    ## Apply
    
    ### 1. Install signal-cli
    
    NanoClaw talks to Signal through signal-cli, which has no bot API of its own.
    Install it if it isn't on PATH yet — Homebrew on macOS, the native release binary
    on Linux (neither needs Java). If it's already installed this is a no-op:
    
    ```nc:run effect:external
    command -v signal-cli >/dev/null 2>&1 || bash setup/install-signal-cli.sh
    ```
    
    ### 2. Copy the adapter and its registration test
    
    Fetch the `channels` branch and copy the Signal adapter and its registration test
    into `src/channels/` (overwrite — the branch is canonical):
    
    ```nc:copy from-branch:channels
    src/channels/signal.ts
    src/channels/signal-registration.test.ts
    ```
    
    ### 3. Register the adapter
    
    Append the self-registration import to the channel barrel (skipped if the line is
    already present). This one line is the skill's only reach-in into core:
    
    ```nc:append to:src/channels/index.ts
    import './signal.js';
    ```
    
    ### 4. Install the QR-rendering dependency
    
    The device-link step renders the linking URL as a terminal QR via `qrcode`.
    Pinned to exact versions — the supply-chain policy rejects ranges and `latest`:
    
    ```nc:dep
    qrcode@1.5.4
    @types/qrcode@1.5.6
    ```
    
    The adapter itself consumes only Node.js builtins, so there is no adapter package
    to install — `qrcode` is purely for rendering the link during setup.
    
    ### 5. Build and validate
    
    Build first: it guards the adapter's typed core-API consumption. Then run the one
    integration test.
    
    ```nc:run effect:build
    pnpm run build
    ```
    ```nc:run effect:test
    pnpm exec vitest run src/channels/signal-registration.test.ts
    ```
    
    `signal-registration.test.ts` imports the real channel barrel and asserts the
    registry contains `signal`. It goes red if the `import './signal.js';` line is
    deleted or drifts, or if the barrel fails to evaluate — so the channel genuinely
    would not register. The adapter has no npm dependency to guard; its typed
    core-API consumption is covered by the build. End-to-end delivery against a real
    Signal account is verified manually once the service runs.
    
    ## Link your Signal account
    
    This is the whole credential step. signal-cli opens a device-link handshake,
    prints a `sgnl://linkdevice…` URL, and renders it as a scannable QR. You scan it
    once from the phone that already runs Signal; that phone's number becomes the
    account NanoClaw sends and receives as — no number is registered.
    
    The device-link runs signal-cli, so it must be reachable first — on `PATH`, or at
    `$SIGNAL_CLI_PATH`. If step 1's install didn't land, the link step has nothing to
    drive; confirm it's present before linking (re-run step 1 if this fails):
    
    ```nc:run effect:check
    command -v signal-cli >/dev/null 2>&1 || [ -x "$SIGNAL_CLI_PATH" ]
    ```
    
    Tell the user:
    
    ```nc:operator
    Link NanoClaw to your Signal account:
    1. On the phone that runs Signal, open Signal → Settings → Linked Devices → Link New Device.
    2. Scan the QR code shown below — or open the `sgnl://linkdevice…` link printed under it on that phone.
    3. Wait for confirmation. The linking URL expires after ~3 minutes; re-run this step for a fresh one.
    ```
    
    Run the device-link. It blocks until you scan, then reports the linked phone
    number back as the account — that number is both your owner handle and the
    conversation address the wiring step needs:
    
    ```nc:run effect:step capture:platform_id=ACCOUNT,owner_handle=ACCOUNT
    pnpm exec tsx setup/index.ts --step signal-auth
    ```
    
    `owner_handle` and `platform_id` both come back as the bare phone number (e.g.
    `+15551234567`). Your assistant reaches you through Signal's Note to Self, so the
    owner conversation is addressed by your own number — not a per-contact UUID.
    
    ## Persist the account
    
    Store the linked number so the adapter binds the right account on start, then sync
    it into the container env:
    
    ```nc:env-set
    SIGNAL_ACCOUNT={{platform_id}}
    ```
    ## Restart
    
    Restart the service so it loads the Signal adapter and binds the account you just
    linked, and wait for its CLI socket before wiring:
    
    ```nc:run effect:restart
    bash setup/lib/restart.sh
    ```
    
    ## Wiring
    
    ### DMs
    
    After the service starts, send any message to the Signal number from your
    personal Signal app. The router auto-creates a `messaging_groups` row. Then:
    
    ```bash
    pnpm exec tsx scripts/q.ts data/v2.db \
      "SELECT id, platform_id FROM messaging_groups WHERE channel_type='signal' ORDER BY created_at DESC LIMIT 5"
    ```
    
    Pass the `id` to `/init-first-agent` or `/manage-channels` to wire it to an agent group.
    
    ### Groups
    
    Add the Signal number to a group from your phone, send any message, then wire the resulting row the same way. Each group gets its own session with the default `shared` mode (one session per agent + messaging group). Create the wiring with `ncl` — **the host service must be running** (`ncl` connects to it over a Unix socket):
    
    ```bash
    # Engage mode/pattern default to the Signal adapter's declared channel defaults
    ncl wirings create --messaging-group-id mg-GROUPID --agent-group-id ag-AGENTID
    ```
    
    ### Grant user access
    
    New Signal users (including the owner's Signal identity) are silently dropped with `not_member` until granted access. After the user's first message appears in `messaging_groups` (host service running):
    
    ```bash
    ncl users create --id "signal:UUID" --kind signal --display-name "<name>"
    ncl roles grant --user "signal:UUID" --role owner
    ncl members add --user "signal:UUID" --group ag-AGENTID
    ```
    
    Find the UUID from `messaging_groups.platform_id` or the `users` table.
    
    ## Next Steps
    
    If you're in the middle of `/setup`, return to the setup flow now. Otherwise wire
    this channel with `/init-first-agent` (or `/manage-channels`).
    
    ## Channel Info
    
    - **type**: `signal`
    - **terminology**: Signal has "chats" (1:1 DMs) and "groups." The owner reaches their own assistant through Note to Self.
    - **platform-id-format**:
      - Owner DM (Note to Self): the bare phone number `+<number>` (e.g. `+15551234567`) — your own messages route back as inbound with `isFromMe`, addressed by your number.
      - Third-party DM: `signal:{UUID}` — the sender's Signal ACI, **not** their phone number.
      - Group: `signal:{base64GroupId}` — base64-encoded GroupV2 ID.
    - **how-to-find-id**: The owner number comes back from the device-link step above. For third parties or groups, send a message to the bot, then query `messaging_groups`.
    - **supports-threads**: no
    - **typical-use**: Personal assistant via Signal DMs or small group chats
    - **default-isolation**: One agent per Signal account. Multiple chats with the same operator can share an agent group; groups with other people should typically get their own agent group (the default `shared` session mode already gives each messaging group its own session).
    
    ### Features
    
    - Markdown formatting — `**bold**`, `*italic*` / `_italic_`, `` `code` ``, ` ```code fence``` `, `~~strike~~`, `||spoiler||` (converted to Signal's offset-based text styles).
    - Quoted replies — `replyTo*` fields populated from Signal quotes.
    - Typing indicators — DMs only (Signal doesn't support group typing).
    - Note to Self — messages you send to your own account from another device route to the agent as inbound with `isFromMe: true`.
    - Voice attachments — detected but not transcribed by default; the agent receives a `[Voice Message]` placeholder. Run `/add-voice-transcription` for local transcription.
    
    Not supported yet: outbound file attachments (logged and dropped), edit/delete messages, reactions.
    
    ## Alternatives
    
    ### Register a dedicated number instead of linking
    
    The device-link above joins Signal as a *secondary device* on an existing number.
    If you'd rather give the assistant its own number, register a dedicated SIM or
    VoIP number that NanoClaw owns entirely. This path takes a captcha, an SMS (or
    voice) verification, and an optional profile name.
    
    > **VoIP numbers:** Signal requires SMS verification before voice. Some VoIP providers are blocked even for voice calls. If registration fails with an auth error, try a different provider or a physical SIM.
    
    **Step 1: Solve the CAPTCHA**
    
    Signal requires a CAPTCHA on first registration:
    
    1. Open `https://signalcaptchas.org/registration/generate.html` in a browser
    2. Solve the captcha
    3. Right-click the **"Open Signal"** button → **Copy Link**
    4. The link starts with `signalcaptcha://` — the token is everything after that prefix
    
    **Step 2: Request SMS verification**
    
    ```bash
    signal-cli -a +1YOURNUMBER register --captcha "PASTE_TOKEN_HERE"
    ```
    
    **Step 3: Voice call fallback (if your number can't receive SMS)**
    
    Wait ~60 seconds after the SMS request, then:
    
    ```bash
    signal-cli -a +1YOURNUMBER register --voice --captcha "SAME_TOKEN"
    ```
    
    Signal calls your number and reads a 6-digit code. The same captcha token is reusable — no need to solve a new one.
    
    > You must request SMS first. Requesting voice immediately fails with `Invalid verification method: Before requesting voice verification…`
    
    **Step 4: Verify**
    
    ```bash
    signal-cli -a +1YOURNUMBER verify CODE
    ```
    
    No output = success.
    
    **Step 5: Set profile name (optional)**
    
    > ⚠ Stop NanoClaw before running signal-cli commands — the daemon holds an exclusive lock on its data directory while running.
    
    Run from your NanoClaw project root:
    
    ```bash
    source setup/lib/install-slug.sh
    
    # macOS
    launchctl unload ~/Library/LaunchAgents/$(launchd_label).plist
    signal-cli -a +1YOURNUMBER updateProfile --name "YourBotName"
    # optionally: --avatar /path/to/avatar.jpg
    launchctl load ~/Library/LaunchAgents/$(launchd_label).plist
    
    # Linux
    systemctl --user stop $(systemd_unit)
    signal-cli -a +1YOURNUMBER updateProfile --name "YourBotName"
    systemctl --user start $(systemd_unit)
    ```
    
    Once registered, set `SIGNAL_ACCOUNT` to this number (as under **Persist the account** above) and restart the service.
    
    ## Optional configuration
    
    These `.env` keys tune how NanoClaw talks to the signal-cli daemon. All are
    optional — the defaults work for the device-link flow above.
    
    ```bash
    # TCP daemon host and port (default: 127.0.0.1:7583)
    SIGNAL_TCP_HOST=127.0.0.1
    SIGNAL_TCP_PORT=7583
    
    # Path to the signal-cli binary (default: resolved on PATH)
    SIGNAL_CLI_PATH=/usr/local/bin/signal-cli
    
    # Whether NanoClaw manages the daemon lifecycle (default: true).
    # Set to false if you run signal-cli daemon externally.
    SIGNAL_MANAGE_DAEMON=true
    
    # signal-cli data directory (default: ~/.local/share/signal-cli)
    SIGNAL_DATA_DIR=~/.local/share/signal-cli
    ```
    
    **Security note:** keep the TCP host on `127.0.0.1`. The daemon has no auth — binding it to a public interface would expose your full Signal account to the network.
    
    ## Troubleshooting
    
    ### Daemon not reachable
    
    ```bash
    grep "Signal" logs/nanoclaw.log | tail
    ```
    
    If you see `Signal daemon failed to start. Is signal-cli installed and your account linked?`:
    - Confirm `signal-cli` is on PATH (or set `SIGNAL_CLI_PATH`)
    - Confirm the account is linked: `signal-cli -a +YOURNUMBER listIdentities` should succeed without prompting
    
    If you see `Signal daemon not reachable at 127.0.0.1:7583` and `SIGNAL_MANAGE_DAEMON=false`, start the daemon yourself: `signal-cli -a +YOURNUMBER daemon --tcp 127.0.0.1:7583`.
    
    ### Bot not responding
    
    1. Channel initialized: `grep "Signal channel connected" logs/nanoclaw.log | tail -1`
    2. Channel wired: `pnpm exec tsx scripts/q.ts data/v2.db "SELECT mg.platform_id, mg.name FROM messaging_groups mg JOIN messaging_group_agents mga ON mg.id = mga.messaging_group_id WHERE mg.channel_type='signal'"`
    3. Service running: `launchctl print gui/$(id -u)/"$(. setup/lib/install-slug.sh && launchd_label)"` (macOS) / `systemctl --user status "$(. setup/lib/install-slug.sh && systemd_unit)"` (Linux)
    4. **Check for duplicate service instances** — if `logs/nanoclaw.error.log` shows `No adapter for channel type channelType="signal"` despite the adapter starting, two NanoClaw processes are racing. See the `/debug` skill section "No adapter for channel type / Messages silently lost" for the full fix.
    
    ### Messages delivered but never arrive (null platformMsgId)
    
    Signal responses show `platformMsgId=undefined` in the main log. This means the delivery poll ran but found no adapter — likely a duplicate service instance issue (see above). Affected messages cannot be retried; the user must resend.
    
    ### Lost connection mid-session
    
    If you see `Signal channel lost TCP connection to signal-cli daemon` in the logs, the daemon dropped the connection. Restart the service to re-establish.
    
    ### Messages dropped with `not_member`
    
    The Signal user hasn't been granted membership. New Signal senders — including the owner's Signal identity — are gated until granted access. `/init-first-agent` grants the owner automatically; for other users, grant access as shown under **Grant user access** in the Wiring section (or via `/manage-channels`) after their first message appears in `messaging_groups`. This affects every new Signal user, since their Signal identity is a separate user record from their identity on other channels even if it's the same person.
    
    ### Captcha required
    
    Signal requires a captcha for new registrations. Go to `https://signalcaptchas.org/registration/generate.html`, solve it, right-click "Open Signal", copy the link, extract the token after `signalcaptcha://`.
    
    ### `Invalid verification method: Before requesting voice verification…`
    
    You must request SMS first, wait ~60 seconds, then request voice. Both steps can use the same captcha token.
    
    ### Config file in use / daemon lock
    
    signal-cli holds an exclusive lock on its data directory while the daemon is running. Stop NanoClaw before running any `signal-cli` commands directly, then restart afterward.
    
    ### Group replies going to DM instead of group
    
    Modern Signal groups use GroupV2. The adapter must extract the group ID from `envelope?.dataMessage?.groupV2?.id` — not `groupInfo?.groupId`, which is GroupV1/legacy. If group messages are routing as DMs, check `src/channels/signal.ts` and confirm the groupId extraction falls through to `groupV2.id`.
    
    ### QR / linking URL expired
    
    The `sgnl://linkdevice…` URL (and the Path A registration captcha) expire after a few minutes. Re-run the device-link step to get a fresh QR.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related