0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI に SSH でコマンドを実行させると端末が無い ― systemctl edit の失敗から決めたこと

0
Last updated at Posted at 2026-09-26

はじめに

Claude Code に、SSH で別の Linux サーバ(検証用の VM)のコマンドを実行させていました。

その中で、人が打てば動くはずのコマンドが、AI 経由だと失敗することがありました。systemctl edit です。

Cannot edit units if not on a tty.

原因は単純で、AI が SSH で送ったコマンドには、端末(TTY)が割り当てられていなかったからです。
ただ、困ったのは失敗したことよりも、そのあと AI が自分で回避策を選んで、そのまま進めたことでした。

この記事は、そのときのログと、そこから決めたルールの記録です。

環境

AI Claude Code(Windows の PC で動かす)
接続先 検証用 VM 2台(AlmaLinux 8.10 / AlmaLinux 9.7)
接続のしかた 小さなシェルスクリプトを通して ssh <接続先> '<コマンド>' の形で実行。コマンド・出力・終了コードを毎回ファイルに残す
ssh のオプション BatchMode=yes、StrictHostKeyChecking=accept-new、ConnectTimeout=10

何が起きたか

テスト用のサービスに、systemctl edit で設定(MemoryMax= と Restart=)を足して、その効き方を確かめるつもりでした。
systemctl edit はエディタを開くコマンドなので、エディタの代わりに「決まった内容をファイルに書くだけ」の小さなスクリプトを SYSTEMD_EDITOR に渡しています。

AlmaLinux 8.10 でのログです(出力の一部をそのまま抜き出しています)。

--- SYSTEMD_EDITOR を差し替えて systemctl edit(対話エディタの代わり) ---
Cannot edit units if not on a tty.
  edit の終了コード: 1
  できたドロップイン:

[B] edit 直後(daemon-reload は自分では打っていない)
    Restart=no
    MainPID=2185
    MemoryMax=infinity

systemctl edit は終了コード 1 で失敗し、ドロップイン(設定を足すファイル)は作られていません。その後の [B] は、設定が入っていない状態(Restart=no、MemoryMax=infinity)のままです。

エディタを差し替えても、systemctl edit は端末が無いと始まりませんでした。 エディタが対話的かどうかではなく、端末があるかどうかを見ているようです。

もう1つ気づいたことがあります。このログ全体の終了コードは 0 でした。 送ったコマンドの中で最後に動いたものが成功していたからです。途中で edit の終了コード を表示させていなければ、失敗に気づけませんでした。

なぜ端末が無いのか

ssh にコマンドを渡して実行すると、既定では端末が割り当てられません。

man ssh_config の RequestTTY は、既定の auto について「ログインのセッションを開くときに端末を求める」と説明しています。

人が ssh でログインしてから打つときは、ログインのセッションなので端末があります。
一方、AI は ssh <接続先> '<コマンド>' の形でコマンドを送ります。これはログインのセッションではないので、端末がありません。

人の手順書どおりに AI に打たせても、同じようには動かないことがある、ということです。

39秒後、AI は自分で回避した

失敗から39秒後、AI は script(1) で疑似端末を用意して、やり直していました。

script -qec "SYSTEMD_EDITOR=/usr/local/bin/zzedit systemctl edit $U" /dev/null

今度は成功しています(同じく AlmaLinux 8.10 のログの一部)。

--- SYSTEMD_EDITOR を差し替え、script(1) で疑似端末を与えて systemctl edit ---
  script 経由の edit の終了コード: 0
  できたドロップイン:
    合計 4
    -rw-r--r--. 1 root root 39  9月 24 00:11 override.conf
    | [Service]
    | MemoryMax=64M
    | Restart=always

AlmaLinux 9.7 でも、同じ方法で終了コード 0 になり、設定が入りました。

結果は正しかったのですが、この回避策を使ってよいかは、誰も判断していませんでした。
私の CLAUDE.md には、端末が要るコマンドについての決まりが無かったからです。AI はその場で考えて、動く方法を選びました。

今回はテスト用のサービスで、戻す手順も決めてあったので問題はありませんでした。
ただ、AI にサーバを触らせる範囲が広がると、「手順どおりに動かなかったときに、AI がその場で選んだ別の方法」を、人が見ないまま進むことになります。

先に決めておくこと

端末が要るコマンドについて、どちらにするかを先に決めて、CLAUDE.md に書いておきます。

A. 端末が要るコマンドは使わせない

たとえば systemctl edit の代わりに、ドロップインのファイルを直接書いて systemctl daemon-reload します。

⚠️ これはサービスの設定を変える操作です。 設定によっては、稼働中のサービスにすぐ効きます(今回の検証では、MemoryMax= と Restart= が daemon-reload だけで稼働中のサービスに効きました)。本番のサービスに当てる前に、壊してよい環境で試してください。

実行の前に、変える設定の今の値を記録しておきます。

# 変更前の値を読む(読み取りだけ)
systemctl show <ユニット名>.service -p <設定名>
# すでにあるドロップインを確かめる(読み取りだけ)
ls -l /etc/systemd/system/<ユニット名>.service.d/ 2>/dev/null

すでにあるドロップインのファイルは、上書きしないでください。 中にほかの設定が入っていることがあります。下の例では、既存のものとぶつからない名前(<名前>.conf)で新しく作ります。

変更のコマンドです。

# <ユニット名>.service に設定を足す
sudo mkdir -p /etc/systemd/system/<ユニット名>.service.d
sudo tee /etc/systemd/system/<ユニット名>.service.d/<名前>.conf >/dev/null <<'EOF'
[Service]
<設定名>=<値>
EOF
sudo systemctl daemon-reload

実行したあとは、同じ systemctl show で値が変わったことを確かめます。
戻すときは、作った <名前>.conf だけを消して sudo systemctl daemon-reload し、同じ systemctl show で変更前の値に戻ったことを確かめます。

この書き方そのものは、今回の検証では試していません。 ドロップインの仕組みは man systemd.unit に書かれています。今回の検証で確かめたのは、ドロップインを消して systemctl daemon-reload するだけで、設定が元に戻ることです。

B. 疑似端末を使ってよいと決める

script -qec "<コマンド>" /dev/null のように、疑似端末を用意して実行することを認めます。今回 AI が選んだのは、この方法です。

認めるなら、使ってよい場面を限って書きます(たとえば「systemctl edit だけ」)。

CLAUDE.md に書く例

- リモートのコマンドには端末(TTY)が無い。端末が要るコマンドは失敗する
- 端末が要るコマンドは <使わない / `script -qec` で疑似端末を与えてよいのは <コマンド名> だけ>
- 手順どおりに動かなかったときは、別の方法を試す前に止まって人間に知らせる

3行目がいちばん大事だと思っています。端末に限らず、「動かなかったときに AI が自分で別の道を探す」こと自体を、先に止めておくためです。

範囲と限界

  • 端末が無くて失敗したのを確かめたのは、AlmaLinux 8.10 の systemctl edit だけです。9.7 では最初から script(1) を使ったので、端末なしの失敗は見ていません。
  • ほかの対話を前提にしたコマンドは、確かめていません。 同じことが起きうる、という以上のことは言えません。
  • A の書き方(ドロップインを直接書く)は、今回は試していません。 確かめたのは、ドロップインを消して daemon-reload すると元に戻ることです。
  • 接続は検証用 VM に root で行いました(検証用なので許可していました)。本番サーバでは、同じにしないでください。
  • 端末が割り当てられない理由は、man ssh_config の RequestTTY の説明にもとづいています。

お気づきの点があればコメントで教えてください。

このルールのもとになったもの

この記事は、AI に SSH でサーバを触らせる前に決めている10項目のうちの1つです。
残りの項目(接続した先が本当にその機械かの確かめ方、パスワードを入力させない理由、終了コード 0 を成功と読ませないこと など)と、使っているスクリプト・チェックリスト・実際のログを、note にまとめています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?