# iPad から Codespaces に接続して Claude Code で開発する — Blink Code + tmux でカジュアルな作業環境を作った

> 机に向かわなくても、ソファでも移動中でも iPad 一枚でこのモノレポに触れる——そのための作業環境を作った記録。数ある iOS ターミナルから Blink Shell を選んだ理由、SSH / Mosh ではなく Blink Code（内蔵 VS Code）で Codespaces に繋ぐと決めた理由、そして回線切替やアプリのサスペンドでトンネルが切れても作業が飛ばないよう tmux を挟んだ理由を、実際に入れた devcontainer 設定と起動スクリプトの一次情報として残します。

- 出典: SUZ-LAB（実験ログ / 一次情報）
- URL: https://suz-lab.co.jp/logs/ipad-blink-codespaces-tmux
- 公開: 2026-07-09
- 更新: 2026-07-09
- 著者: Hiro（主任研究員・https://suz-lab.co.jp/researchers/hiro・X: https://x.com/suz_lab_hiro）
- 柱: テクノロジー
- タグ: #iPad #Blink Shell #Codespaces #tmux #DevContainer #VS Code #Claude Code #AI
- カバー画像: https://suz-lab.co.jp/assets/logs/ipad-blink-codespaces-tmux.png?v=264d342c

---


## 何をしたかったか — 「カジュアルに触れる」を環境で担保する

このリポジトリは Codespaces 上の Dev コンテナで回している。母艦は PC だが、**PC の前に
座っているときしか手が動かせない**のはもったいない。ちょっとした修正、ログの下書き、
Claude Code に投げておきたい調べ物——そういう軽い作業は、ソファでも移動中でも、
iPad 一枚でやれたほうがいい。

狙いは速く書くことではなく、**着手の敷居を下げること**だ。思いついたときにその場で
コンテナへ入り、`claude` を叩いて対話を始められる。だから iPad 側の環境に求めた要件は
はっきりしている。

- **同じコンテナにそのまま入れる。** ローカルに別環境を組まない。PC で使っている
  `/workspaces/suz-lab` の Codespace に、iPad からもそのまま接続する。
- **ターミナルとエディタの両方が要る。** Claude Code / pnpm / git を叩くターミナルに加え、
  差分を眺めたりファイルツリーを開いたりする GUI も欲しい。
- **切れても作業が飛ばない。** iPad はすぐスリープするし、Wi‑Fi ↔ 5G の回線切替も日常だ。
  そのたびに実行中のコマンドが死ぬようでは「カジュアル」にならない。

この 3 点を満たす組み合わせとして、**Blink Shell（Blink Code）+ tmux** に落ち着いた。
以下、なぜその一つひとつを選んだのかを、代替案を潰しながら残す。

## なぜ Blink Shell か — 「VS Code を内蔵した iOS ターミナル」は実質これだけ

iOS の SSH / ターミナルアプリは他にもある（Termius、Secure ShellFish、a‑Shell、iSH…）。
それでも [Blink Shell](https://blink.sh/) を選んだのは、要件の 2 つめ——**ターミナルと
エディタの両方**——を一つのアプリで満たせるのが実質これだけだったからだ。

- **Blink Code（内蔵 VS Code）を持っている。** 多くの iOS ターミナルは「SSH クライアント」で
  止まる。Blink は「Build & Code」版で **VS Code を内蔵**し、そこから GitHub Codespaces に
  直接つなげる。ターミナルとファイルツリー・拡張機能（Claude Code 拡張・Copilot）を
  同じアプリの中で行き来できる。
- **モバイル本気仕様。** 外部キーボードのフルサポート、Mosh 内蔵、外部ディスプレイ出力など、
  「iPad で腰を据えて書く」ことを前提に作られている。ターミナルとしての完成度が高いので、
  ここで作業する前提に立てる。

要するに、Blink を選んだ決め手は「SSH が速い」ではなく、**Codespaces + VS Code の体験を
iPad に持ち込めるのがこれだった**という一点に尽きる。

## なぜ SSH / Mosh ではなく Blink Code（内蔵 VS Code）接続か

iPad から Codespaces に入る経路は、大きく分けて 2 つ考えられた。**(A) Blink のローカル
シェルから SSH / Mosh で入る**、**(B) Blink Code（内蔵 VS Code）でトンネル接続する**。
結論から言うと (B) を正規ルートにした。理由はそれぞれの経路の性質にある。

### SSH が Blink 単体で成立しにくい

Codespaces への SSH は、初回に `gh`（GitHub CLI）でのセットアップが要る
（`gh codespace ssh`、あるいは `gh codespace ssh --config` で SSH 設定を書き出せば以後は
素の `ssh <codespace>` でも繋がる）。ところが **Blink のローカルシェルには `gh` が無く、
iOS では追加インストールもできない**。つまり SSH を成り立たせるための初期セットアップ
そのものが、Blink 単体では困難だ（要望として上がっている:
[blinksh/blink#1497](https://github.com/blinksh/blink/issues/1497)）。

Codespace の SSH エンドポイントは生の `host:port` として素直に到達できるわけではなく、
`gh` のトンネル越しに張る前提になっている。この「まず `gh` で橋を架ける」段が iPad で
踏めない以上、SSH をカジュアル運用の土台には据えられない。

### Mosh の利点は、この構成では tmux と重複する

Mosh（Mobile Shell）は本来 iPad と相性がいい。UDP ベースでローカルエコーを持ち、
**回線切替やスリープをまたいでもセッションが生き残る**——まさにこちらが欲しい性質だ。
それでも採らなかった理由は 2 つ。

- **ブートストラップが SSH と同じ壁にぶつかる。** Mosh は接続の入口に SSH を使い、
  そのうえで UDP を張る。SSH の初期セットアップ（`gh`）が踏めない以上、Codespaces の
  ポートフォワード方式の上で Mosh を素直に成立させるのは難しい。
- **Mosh はターミナルしかくれない。** Mosh が守ってくれるのは端末セッションだけで、
  要件 2 つめの GUI（差分・ファイルツリー・拡張機能）は別に用意することになる。

そして本質的なのはここだ——**Mosh が transport 層（UDP）で解く「切れても生き残る」を、
こちらはサーバー側の tmux で解く**（次節）。tmux を入れた時点で、接続経路が VS Code の
トンネルであっても「切れても作業は飛ばない」が成立する。だから Mosh の主な利点は
**重複**し、しかも Mosh では得られない VS Code のエディタ体験まで一度に手に入る (B) の
ほうが、要件に対して素直だった。

### だから Blink Code（内蔵 VS Code）を正規ルートにする

(B) なら `gh` のブートストラップを踏まずに Codespace へ繋がり、同じアプリの中で
ターミナルと GUI の両方が使える。Claude Code は VS Code 拡張として動き、pnpm の
開発サーバはポートフォワードでプレビューできる。要件の 2 つめと 3 つめを、一つの経路で
まとめて満たせる。

接続は **URL を `code` に渡すのが確実**だった。Codespace の URL をそのまま `code` の
引数に渡す。

```bash
code https://github.com/codespaces/<あなたの-codespace>
```

**「素の VS Code を先に起動してから拡張機能で Codespace を選ぶ」のではなく、最初から
URL を `code` に渡す**のが要点だ。順序を間違えると、Blink はトンネルを張らずに
ブラウザ窓（`*.github.dev`）へフォールバックしてしまう（既知挙動:
[blinksh/blink#1674](https://github.com/blinksh/blink/issues/1674)）。左下ステータスに
**`>< Codespaces`** と出ていれば接続成功。初回だけ `code .` を一度実行して Code ビューを
起こし、案内に従って **`blink-fs` 拡張機能**（Blink のファイルシステム／トンネル連携の
前提）を入れておく。

> 接続を **非対話（ノンインタラクティブ）**にしておくのも重要だった。途中でパスワードや
> 2FA のプロンプトが出ると、Blink Code がトンネルを張れずブラウザ窓に落ちる。

## なぜ tmux を挟むか — 「切れても作業が飛ばない」をサーバー側で担保する

要件の 3 つめが、この環境の肝だった。iPad はスリープするし、Wi‑Fi ↔ モバイル回線の
切替も頻繁に起きる。**そのたびに Blink Code のトンネルは切れる**。素のシェルで `claude`
や長いビルドを走らせていると、トンネルが切れた瞬間にプロセスごと道連れになる。

これを **transport 層（Mosh）ではなく、コンテナ側の tmux で解く**ことにした。tmux の
セッションはトンネルとは独立してコンテナ内で生き続けるので、iPad が切れても実行中の
コマンドはそのまま走り続ける。ターミナルを開き直すと同じセッションへ再アタッチして、
**中断したところから作業を継続できる**。接続経路が何であっても効くのが、サーバー側で
解く利点だ。

tmux にした理由は、余計な依存を足さずにこれが賄えるから。`screen` などの選択肢もあるが、
ペイン分割・マウス操作・`window-size` 制御まで含めて iPad から素直に扱えるのは tmux で、
apt で入れるだけで済む。

## 実際に入れた設定 — VS Code のターミナルプロファイルを tmux 既定にする

「毎回 `tmux attach` と打つ」では敷居が下がらない。**ターミナルを開いたら自動で永続
セッションに入る**ようにしたい。そこで tmux を apt パッケージに加え
（`.devcontainer/devcontainer.json`）、VS Code 統合ターミナルの既定プロファイルを
**tmux ランチャー**にした。

```jsonc
// .devcontainer/devcontainer.json（抜粋）
"terminal.integrated.defaultProfile.linux": "tmux",
"terminal.integrated.profiles.linux": {
  "tmux": {                                   // 既定: 永続セッションでログインシェル
    "path": "bash",
    "args": ["${workspaceFolder}/.devcontainer/start-tmux.sh"],
    "icon": "terminal-tmux"
  },
  "Claude Code": {                            // 別プロファイル: tmux 内で claude を起動
    "path": "bash",
    "args": ["${workspaceFolder}/.devcontainer/start-claude.sh"],
    "icon": "sparkle"
  },
  "bash": { "path": "bash", "icon": "terminal-bash" }  // tmux を挟まない素のシェル
}
```

ランチャースクリプト（`start-tmux.sh`）が肝で、実運用でつまずいた点をそのまま
反映している。

- **ログインシェルを明示起動する。** tmux はコマンド未指定だと `$SHELL` を「非ログイン」で
  開き、`.bash_profile` / `.profile` の初期化（PATH など）が走らない。だから
  `tmux new-session -d -s "$SESSION" "$SHELL" -l` と**明示的にログインシェル**で開く。
- **iPad 向けのセッション設定を毎回冪等に当てる。** `-g`（サーバ全体）で
  `mouse on`（タッチでスクロール／ペイン選択）と `window-size latest`（最後にアタッチした
  クライアントのサイズに追従＝別端末やスリープ復帰で画面サイズが変わっても崩れにくい）を、
  新規／既存に関わらず毎回当てる。作成分岐の中に置くと既存セッションに効かないので外に出した。
- **`-d` で奪い返す。** `tmux attach-session -d` で既存クライアントを切り離してから
  アタッチする。スリープ復帰後に古い接続が宙ぶらりんで残っていても、新しいこの
  ターミナルから確実に操作できる。
- **失敗したら素のシェルへフォールバックする。** tty が無い・TERM 非対応などで attach が
  失敗すると tmux は終了コード 1 を返す。それがプロファイルの終了コードになると
  「終了コード 1 で終了しました」とターミナルごと落ちてしまうので、作成・アタッチとも
  失敗時は `exec "$SHELL" -l` に逃がし、**少なくとも作業は続けられる**ようにした。

`claude` を tmux 内で自動起動する **"Claude Code" プロファイル**（`start-claude.sh`）も
別に用意した。既定セッション名を分けてある（tmux 側が `main`、Claude 側が `claude`）ので、
**両者は同じ tmux サーバの別セッションとして共存**し、互いを奪い合わない。Claude Code を
終了してもセッションは閉じず、そのまま同じ tmux で git やビルドを打てる。

## つまずき（トラブルシューティング）

- **VS Code 内でなくブラウザ窓（`*.github.dev`）で開いてしまう。** 前述の順序問題。
  `code <Codespace の URL>` で直接開く／接続を非対話にする／Blink を最新版にする、で解消。

## 検証

- Codespace の URL を `code` に渡して接続 → 左下に `>< Codespaces` を確認。統合ターミナルを
  開くと tmux セッション（`main`）に自動アタッチし、`claude` / `pnpm` / `git` が通ること。
- **切断 → 復帰の往復**を実際にやった。長いコマンドを走らせたまま iPad をスリープ／回線を
  Wi‑Fi ↔ 5G に切替 → トンネルが切れる → ターミナルを開き直すと同じ tmux セッションに
  再アタッチし、コマンドが継続していること。
- "Claude Code" プロファイルが `claude` セッションを別に張り、`main` と共存すること。
  tmux の無い経路・ネスト時に素のシェル／素の `claude` へフォールバックすること。
- 触れた設定・スクリプトは `pnpm typecheck` / `pnpm lint` が通ること（devcontainer 側の
  変更なのでアプリのビルドには影響しない）。

## 学び

- **「切れても飛ばない」を transport で解くか、サーバー側で解くか。** Mosh は前者、tmux は
  後者。今回は接続経路（Blink Code のトンネル）に依存せず効く後者を選んだ。おかげで
  「iPad で回線が不安定」という一番痛い前提を、経路の選択と切り離して潰せた。
- **ツールは「速さ」でなく「要件の重なり」で選ぶ。** Blink を選んだのは SSH が速いからでなく、
  ターミナル＋VS Code＋Codespaces が一つに重なるのがこれだったから。Mosh を採らなかったのも、
  その利点が tmux と重複したから。手持ちの制約（`gh` を踏めない iPad）から逆算すると、
  選択は自ずと絞れる。
- **カジュアルさは自動化で担保する。** 「開いたら永続セッションに入る」「切れても戻れる」
  「失敗しても素のシェルに落ちる」——このあたりを既定プロファイルとフォールバックで
  仕込んでおくと、iPad を開いてから作業に入るまでの摩擦がほぼゼロになる。着手の敷居を
  下げるという当初の狙いは、結局この地味な自動化の積み重ねで達成された。

