痛み: 「/home/yousan/.claude/usage-data/report-...html です」と言われても開けない
OpenClaw で AI エージェントを動かしていると、エージェントは仕事の成果物をホスト上にファイルとして生成します。HTML のレポート、PDF、動画、スクショ、CSV。そして生成した結果としてエージェントが返してくるのは 「ホスト上の絶対パス」 です。
できました: /home/yousan/.claude/usage-data/report-2026-06-12-111053.html
これを Discord や LINE 越しに受け取った私は、開けません。
- ファイルパスは URL ではありません。Discord でクリックしても何も起きない
-
同じマシンに居ても、Finder で
~/Library/...を掘るなりopen <path>を打つなりが要る - 別マシンに居ると SCP するか SSH してブラウザを開くしかない
- iPhone から見ようとした瞬間に詰む。SCP も SSH も実用的ではない
私の環境では、OpenClaw が動いているのは Windows 上の WSL Ubuntu (tachikoma)、私が触っているのは Mac、外では iPhone、というクロスホスト・クロスネットワーク構成です。そのため エージェントが何かファイルを作るたびに「見るための儀式」が発生 していました。これがなかなかしんどいわけです。
エージェントの成果物の大半は HTML/画像/PDF/動画なので、本来は HTTP(S) で URL になっていれば、デバイス問わずブラウザで一発 のはずです。これをどうにかしたい、というのが今回の話です。
結論: tailnet 限定の Caddy 配信 + 日付別 share/ + エージェントへの規約
採用した形はこれです。
-
ホスト: OpenClaw が動いているマシン(私の場合は
tachikoma) -
bind: Tailscale のノード IP に固定(
100.x.y.z)。0.0.0.0にはしない -
配信: Caddy の
file_server browseを systemd の user unit として常駐 -
ルート:
~/.openclaw/workspace/share/ -
構造:
share/YYYY-MM-DD/<topic-slug>/ - 置き方: 元ファイルは触らず symlink を置くだけ
- アクセス: Tailscale MagicDNS でホスト名解決。Mac でも iPhone でも tailnet に入っていればそのままブラウザで開ける
これで「ファイルパス問題」は次のように形が変わります。
- エージェントは生のホストパスを返す代わりに、
share/YYYY-MM-DD/<topic-slug>/に symlink を作って URL を返す - 私は Discord で URL を受け取り、Mac でも iPhone でもタップ一発で開く
- SCP も SSH も Finder も登場しない
そして地味に効くのが Tailscale で、これが2つの問題を同時に潰してくれます。
- ネットワーク問題: WSL Ubuntu と Mac と iPhone は通常別ネットワークですが、tailnet 内なら直接到達できます
-
ホスト名問題: MagicDNS のおかげで IP を覚えなくて済みます(
http://tachikoma:8801/)
さらに share/YYYY-MM-DD/ で日付別に切ってあるので、Caddy の autoindex を辿るだけで「今日エージェントが何を作ったか」を一覧で見渡せます。
当日のディレクトリに潜ると、その日に生成された成果物がトピック単位で並びます。Caddy の file_server browse には Grid 表示と画像サムネが付いてくるので、生成画像を渡すときはこちらのほうが圧倒的に見やすいです。実際の成果物(HTML レポートなど)も、ディレクトリ URL を開けばそのままブラウザに表示されます。
なぜ Caddy なのか — 候補3つを試した結果
最初は python3 -m http.server で立てていました。これは確かに動くのですが、autoindex の見た目が <ul><li> の素の HTML で、ファイルが増えてくると一覧性が落ちます。スマホから見ると文字も小さくてつらいです。
そこでファイラ系UIを持つ候補をいくつか試しました。
| 候補 | symlink 追従 | UI | systemd 化 | 認証 |
|---|---|---|---|---|
python3 -m http.server |
する | 素のテキスト一覧 | 容易 | 無し |
| filebrowser | しない1 | リッチなファイラ | 容易 | あり |
| dufs | する | テーブル型 | 容易 | あり |
Caddy file_server browse |
する | List/Grid/サムネ/検索/ソート | 容易 | リバプロで足せる |
結局 Caddy に落ち着きました。理由は単純で、
- symlink を素直に追従する。share の外を指す symlink でも何の設定もなく見える(これは大きい)
- Grid 表示と画像サムネ がデフォルトで効く。生成画像が多い私のユースケースに合う
-
ダークモード が OS の
prefers-color-schemeに追従する。夜の iPhone でまぶしくない - 検索・ソート・パンくず が全部入り。ロード時に JS が走る系ではなく、サーバ側 HTML なので軽い
- シングルバイナリ。Go 製で 50MB 弱、依存ゼロ
- 設定は Caddyfile 8行。実質ワンライナーみたいなもの
filebrowser が外れたのは認証 UI のせいではなく、純粋に symlink の挙動です。ユーザが share の中だけにファイルを置く運用に変えれば filebrowser でもいけますが、本記事の「元ファイルは触らない」原則と相性が悪いです。dufs は問題なく動きますが、テーブル型 UI で Grid・サムネが無い、というだけです。
構築
1. ディレクトリを切る
mkdir -p ~/.openclaw/workspace/share
2. Caddy を入れる
公式のシングルバイナリを取ってくるのが一番速いです。
mkdir -p ~/bin
curl -sL "https://caddyserver.com/api/download?os=linux&arch=amd64" -o ~/bin/caddy
chmod +x ~/bin/caddy
~/bin/caddy version
apt のパッケージ(caddy)でも構いませんが、share 配信用途では root 権限を要求してまで /etc/caddy/Caddyfile を使う必要はありません。
3. Caddyfile を書く
~/.config/caddy/Caddyfile:
{
admin off
auto_https off
}
:8801 {
bind 100.100.26.90
root * /home/yousan/.openclaw/workspace/share
file_server browse
encode gzip
}
ポイントは2つです。
-
bindで listen インターフェースを tailnet IP に絞る。Caddy のリスナーは、サイトアドレスに IP を書いてもデフォルトで全インターフェース (0.0.0.0) に bind されます。tailnet 限定にするにはbind 100.100.26.90を明示するのが必須で2、これを忘れると家 LAN 全体にポートが開きます。そしてサイトアドレスは ホストを書かず:8801にします — ここにホスト名や IP を書くと Host マッチが効いてしまい、http://tachikoma:8801/(MagicDNS 名)でのアクセスが弾かれるからです3 -
auto_https off。tailnet 内通信なので TLS は要りません。これを書かないと Caddy が証明書を取りにいこうとします
4. systemd user unit を書く
~/.config/systemd/user/share-http.service:
[Unit]
Description=Workspace share static HTTP server (Caddy, tailnet only)
After=network-online.target tailscaled.service
Wants=network-online.target
[Service]
Type=simple
ExecStart=%h/bin/caddy run --config %h/.config/caddy/Caddyfile --adapter caddyfile
Restart=on-failure
RestartSec=3s
[Install]
WantedBy=default.target
tailnet IP は Caddyfile 側で持つので、unit はシンプルです。
5. 起動して enable
systemctl --user daemon-reload
systemctl --user enable --now share-http.service
systemctl --user status share-http
systemctl --user を使うので、サーバマシンでは loginctl enable-linger $USER4 を一度叩いておくと、ログアウト後も生き続けます。
6. エージェントへの規約として書き起こす(ここが本題)
サーバが立っているだけでは何も解決しません。エージェントが 「成果物のパスを返す代わりに symlink を置いて URL を返す」 ようになって初めて、痛みが消えます。
OpenClaw には memory/ という長期メモリ機構があって、エージェントは起動時にこれを参照します5。ここに「ファイルを渡すときのルール」を書いておけば、すべてのエージェントが同じ規約に従って成果物を出してくれるようになります。
私が memory/tailscale-static-hosting.md に書いているのはこれです。
---
name: tailscale-static-hosting
type: feedback
---
yousan にファイル/動画/レポート等をブラウザで渡すときは、毎回新ポートを立てずに統合 share 領域に置く。
**Why:** 過去にエージェントが各自でポート立てて入口が散乱した。
yousan が「どこ見ればいい?」を毎回確認しないと辿れなかった。
**How to apply:**
- ホスト: tachikoma, tailscale IP 100.100.26.90
- 配信プロセス: share-http.service (systemd user unit, Caddy)
- ルート: /home/yousan/.openclaw/workspace/share/
- 構造: share/YYYY-MM-DD/<topic-slug>/
- 実体: 元ファイルは触らず symlink を置く
- URL: http://tachikoma:8801/YYYY-MM-DD/<topic-slug>/
- 新規依頼が来たら: share/YYYY-MM-DD/<slug>/ を作る → symlink → URLを渡す。
サービス常駐済みなので再起動不要
- 禁止: 別ポートで HTTP サーバを新規に立てる /
Caddyfile の bind から tailnet IP を消す(全インターフェース公開になる)
ポイントは 「Why」と「How to apply」を両方書く ことです。Why だけだと「で、どうすればいいんだ」になりますし、How だけだと「これ何のためのルール?」になって、エージェントがエッジケースで判断を誤ります。
これさえ書いておけば、「このファイルをホスティングしてほしい」と言うだけで、エージェントは自動的に symlink を置いて URL を返してきます。実際のやり取りはこんな感じで、/home/yousan/.claude/usage-data/... というホストパスは二度と Discord に流れてきません。
エージェントが実際にやっていること
参考までに、依頼を受けたエージェントが内部的に叩いているコマンドはこんな感じです。
mkdir -p ~/.openclaw/workspace/share/2026-06-12/usage-report
ln -sf /home/yousan/.claude/usage-data/report-2026-06-12-111053.html \
~/.openclaw/workspace/share/2026-06-12/usage-report/index.html
index.html という名前にすると、ディレクトリ URL を叩いたときに Caddy の file_server がそれを返します6。autoindex のディレクトリリストが欲しいときは index.html を置かなければよいです(browse を有効にしているのでリストが生成されます)。
返ってくる URL はこうなります。
http://tachikoma:8801/2026-06-12/usage-report/
MagicDNS のおかげで tachikoma というショートホスト名で到達できます7。Mac でも iPhone でも、tailnet に入っていればそのままタップで開けます。
MagicDNS が無いノードからは FQDN (tachikoma.tail087bcf.ts.net) で行けます。
クロスデバイスで効くポイント
これを入れたあとで一番ありがたかったのは iPhone から開けるようになったこと でした。
| デバイス | 以前 | 今 |
|---|---|---|
| 同じホストの Linux |
xdg-open <path> で開く |
URL タップ |
| Mac (別ホスト) | SCP / sshfs / SSH 経由でブラウザ | URL タップ |
| iPhone | 事実上見られない | URL タップ |
「事実上見られない」が「URL タップ」になるだけで、外出中にエージェントから来た成果物を確認できるようになりました。これは副次的ですが、一番大きい効果でした。
加えて、
- 過去成果物が消えない: ポートが死なないので、3日前の URL を共有しても今でも開ける
-
露出範囲がブレない: 全エージェントが tailnet IP bind を使う規約なので、
0.0.0.0事故が起きる隙間がない - 元ファイルが汚れない: symlink なので、エージェントが「ホスティング用にコピー作ります」をやらない
- Grid 表示が画像レビューに強い: 生成画像をブラウザで一気にレビューできる
- autoindex が地味に効く: 何があったか日付単位で振り返れる
まとめ — 「こう書いておけば楽」
ここまでの話を OpenClaw に与えるルールに圧縮すると、こうなります。
- OpenClaw が動くホストに Caddy を tailnet IP に bind して systemd user unit として常駐させる
- クライアント (Mac, iPhone) を Tailscale で同じ tailnet に入れる
-
memory/tailscale-static-hosting.mdのような形で エージェントに「成果物は share/YYYY-MM-DD// に symlink、URL を返す」と書く - 元ファイルは触らない、ポートは増やさない、tailnet IP bind は外さない、と明記する
これだけで「エージェントが返すホストパスをどう開くか問題」がほぼ消えます。ファイル単位の SCP も SSH も Finder も要らなくなり、Mac でも iPhone でも URL タップだけで成果物にアクセスできるようになります。
参考リンク
- Caddy
- Caddy: file_server ディレクティブ
- Caddy: bind ディレクティブ
- Caddy: encode ディレクティブ
- Caddy: Caddyfile の構文
- systemd: ユニットファイル仕様
- systemd: loginctl と linger
- Tailscale
- Tailscale MagicDNS
- Tailscale ACL
- OpenClaw
- filebrowser
- dufs
-
filebrowser は
--rootの外を指す symlink を一覧に出さない仕様です(2026-06 時点)。本記事の運用は「エージェントが各自の保存先に書く → share/ から symlink で参照」が前提で、symlink 先は基本 share の外なので、filebrowser だと一覧が空に見える事故が起きます。 ↩ -
Caddy のリスナーはデフォルトでワイルドカード(全インターフェース)に bind します。サイトアドレスにホストや IP を書いても、それは受け付ける
Hostヘッダのマッチに使われるだけで、listen するインターフェースは変わりません。特定 IP のインターフェースだけで listen させるにはbindディレクティブ が必須です。 ↩ -
Caddy はサイトアドレスのホスト部分を
Hostマッチャとして扱います。100.100.26.90:8801と書くとHost: 100.100.26.90:8801のリクエストしか通らず、http://tachikoma:8801/のようなホスト名アクセスは弾かれます。ホスト部分を省いて:8801とすれば任意のHostにマッチするので、IP でもホスト名でも到達できます。 ↩ -
systemd の linger を有効にすると、ユーザがログインしていなくても user manager が起動し続けます。常駐 user unit には必須の設定です。 ↩
-
OpenClaw のエージェントワークスペース(
~/.openclaw/workspace/)にはAGENTS.mdMEMORY.mdmemory/といったファイルがあり、セッション開始時に読み込まれる仕組みになっています。詳細は OpenClaw のドキュメント を参照してください。 ↩ -
Caddy の
file_serverは要求パスがディレクトリの場合、既定でindex.html/index.txtを順に探して、見つかればそれを返します。なければbrowse設定が有効なときに限ってディレクトリリストを HTML で生成します。 ↩ -
Tailscale MagicDNS は tailnet 内のホスト名を自動で名前解決する仕組みです。各ノードの
hostnameで他ノードからアクセスできます。tailnet に参加していない端末からは名前も解決できませんし、TCP も通りません。 ↩


