LINE から Claude Code を叩く ─ AI 作業環境をスマホに持ち出す

« 前の記事

外出先のスマホから自宅の Claude Code に話しかけられたら、と考えたのが発端だ。PCの前に座らなくても、移動中に「あのサーバーのログ確認しておいて」と投げておける。そういう使い方がしたくて、LINE Messaging API と Claude Code CLI をつなぐ Webhook サーバーを自宅に立てた。

LINE を選んだ理由は単純で、スマホに最初から入っていて、通知が来て、日本語入力がしやすい。専用アプリを別途開く手間がなく、家族や友人との会話の合間に「ちょっと確認」できるのが肌に合っていた。SSH クライアントアプリを立ち上げてログインして…という手順がいらないのが地味に大きい。

全体構成

LINE アプリ
   │ (Messaging API)
   ▼
LINE Platform ── Webhook ──▶ 自宅サーバー (Node.js)
                                  │
                                  ▼
                            Claude Code CLI
                                  │
                                  ▼
                            応答を LINE へ返信

ポイントは「Claude Code を CLI として呼び出す」こと。Anthropic の API を直接叩くのではなく、ローカルの作業ディレクトリで動く Claude Code にメッセージを渡す。リポジトリのファイルを参照できるし、ツール実行もそのまま使える。「このファイルどうなってる?」という質問に対して実際のコードを見て答えてくれるのが、APIを直叩きするより便利な点だ。

構築手順

1. LINE Developers でチャネルを作成

LINE Developers コンソールにアクセスし、プロバイダーを作成してから Messaging API チャネルを追加する。作成後に以下の2つを必ず控えておく。

  • チャネルアクセストークン(Messaging API 設定タブ → 「発行」ボタン)
  • チャネルシークレット(チャネル基本設定タブ)

応答メッセージとあいさつメッセージは OFF にしておく。ON のままだとこちらの返信とシステムの自動応答が二重に届いて混乱する。

2. Webhook サーバーを立てる

Node.js + Express で実装した。

import express from 'express';
import { spawn } from 'child_process';
import crypto from 'crypto';

const app = express();
app.use(express.json({
  verify: (req, res, buf) => { req.rawBody = buf; }
}));

app.post('/webhook', async (req, res) => {
  if (!verifySignature(req)) return res.sendStatus(401);

  const event = req.body.events?.[0];
  if (!event || event.type !== 'message') return res.sendStatus(200);

  // LINE には先に 200 を返してから処理する(タイムアウト対策)
  res.sendStatus(200);

  const reply = await runClaudeCode(event.message.text);
  await pushMessage(event.source.userId, reply);
});

function runClaudeCode(prompt) {
  return new Promise((resolve, reject) => {
    const proc = spawn('claude', ['--print', prompt], {
      cwd: '/path/to/your/project',
    });
    let output = '';
    proc.stdout.on('data', (d) => output += d.toString());
    proc.on('close', () => resolve(output.trim()));
    proc.on('error', reject);
    setTimeout(() => { proc.kill(); reject(new Error('timeout')); }, 60000);
  });
}

app.listen(3000);

res.sendStatus(200) を先に返してから処理を始めるのが重要なポイントだ。これについては後で詳しく触れる。

3. 署名検証を必ず入れる

Webhook URL は外部に公開するエンドポイントになるため、LINE Platform 以外からの POST を弾く処理が必須だ。x-line-signature ヘッダーを HMAC-SHA256 で検証する。

function verifySignature(req) {
  const signature = req.headers['x-line-signature'];
  if (!signature) return false;

  const hmac = crypto.createHmac('sha256', process.env.LINE_CHANNEL_SECRET);
  hmac.update(req.rawBody);
  const digest = hmac.digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(digest),
    Buffer.from(signature)
  );
}

これを忘れると、URL が知れ渡った瞬間に任意のメッセージを送りつけられる踏み台になる。crypto.timingSafeEqual を使うのは、単純な文字列比較だとタイミング攻撃に弱いためだ。

4. HTTPS で公開する

自宅サーバーなら Let’s Encrypt で証明書を取得し、Nginx をリバースプロキシとして立てるのが手軽だ。

server {
  listen 443 ssl;
  server_name your-domain.example.com;

  ssl_certificate     /etc/letsencrypt/live/your-domain.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/your-domain.example.com/privkey.pem;

  location /webhook {
    proxy_pass http://localhost:3000;
  }
}

LINE Developers コンソールの「Webhook 設定」に URL を登録して「検証」ボタンが成功すれば疎通完了。

ハマりどころ

Reply Token の 30 秒タイムアウト

LINE の Reply Message API は、Webhook を受け取ってから 30 秒以内に返信しなければ Reply Token が失効する。Claude Code の処理は長いプロンプトだと余裕で 30 秒を超えるため、Reply API のままでは使い物にならなかった。

解決策は Push Message API に切り替えること。ユーザー ID を保持しておき、処理が終わったタイミングで Push で送る。前述のコードで res.sendStatus(200) を先に返しているのはこのためで、LINE には即座に 200 を返し、裏で処理を続けて最後に Push する流れにしている。

5000 文字制限

LINE のメッセージ 1 件あたりの上限は 5000 文字。Claude Code の応答はコードを含むと簡単に超える。長い場合はチャンクに分割して複数メッセージで送るか、「要点だけ答えて」という条件をプロンプトに足すと安定する。

セッション管理の難しさ(これが一番頭を悩ませた)

最初はシンプルに「メッセージを受け取ったら Claude Code に渡す」だけで十分だと思っていた。ところが実際に使い始めると、複数のプロジェクトを話題ごとに切り替えたくなってくる。WordPress の記事修正を頼んでいた途中で、サーバーの設定確認に話題を移す、といった使い方だ。

そこでプロジェクトを切り替えるコマンド(/switch server のような)を実装してみた。ところが問題が起きた。前のプロジェクトで交わした会話のコンテキストが残っていて、別のプロジェクトの質問に対して的外れな答えが返ってくるのだ。直前の会話が「WordPress のプラグインの話」だったせいで、サーバー設定の質問に WordPress 絡みの回答をされた、といった具合に。

Claude Code の --print モードはステートレスなので、コンテキストの持ち越しはこちら側で制御しなければならない。

  • 会話履歴をどこまで渡すか(全部渡すと古い文脈を引きずる、短くすると前の発言を参照できない)
  • プロジェクト切り替え時にどのタイミングで履歴をリセットするか
  • 暗黙の話題転換をどう検知するか

この3点がまだ試行錯誤の最中で、完全な答えは出ていない。今のところ「/reset コマンドで明示的に履歴をクリアする」という力技で運用しているが、気を抜くと忘れてコンテキストが混ざる。

セッション管理をうまく設計できれば、このツールの実用性は格段に上がると思っている。もう少しいい方法が見つかったら、別の記事にまとめる予定だ。

使ってみての所感

運用してみて一番実感するのは、「スマホから話しかける」というインターフェースの気軽さだ。PCを開かなくても、移動中や家事の合間に「あのサーバー、ディスク残量どのくらいだっけ」と聞ける。SSH でログインして確認するより心理的なハードルがずっと低い。

セッション管理の問題はまだ解決しきれていないが、単一プロジェクトの範囲で使う分には十分実用になっている。スマホから「サーバーのログ見て」と話しかけるだけで状況が返ってくるのは、想像以上に体験がいい。自宅サーバーをもっと気軽に触れるようになった感覚がある。

参考リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です