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?

Codex CLI「writes」承認モードを試したら、プロンプトは減らなかった

0
Posted at

はじめに

2026年7月9日、Codex CLI 0.144.0がリリースされた1。リリースノートの目玉のひとつが「writes app-approval mode」だ。公式の説明はこうなっている。

Added a writes app-approval mode that allows declared read-only actions while prompting for writes.
Release 0.144.0(openai/codex)(2026-07-09)

「read-only と宣言されたアクションは確認なしで実行し、書き込みだけプロンプトする」という説明を読む限り、MCPツールを多用するワークフローでは承認プロンプトの数がかなり減りそうに見える。筆者は普段からCodex CLIに自作MCPサーバーを繋いで検索・参照系のツールを使っているので、「これで検索ツールの確認が消えるなら地味に嬉しい」と期待して試した。

結論から言うと、期待は外れた。「read-only と宣言された」の主語はMCPサーバー側であり、サーバーがその宣言を怠っているツールは、writesモードでも普通に書き込み扱いされる。 しかも非対話実行(codex exec)では、承認待ちのツール呼び出しは自動でキャンセルされるため、プロンプトが「増える」どころか黙って失敗する。

この記事で学べること

  • writes 承認モードの正しい設定キー(apps.*.default_tools_approval_mode)と、--strict-config を使った検証手順
  • MCP仕様の readOnlyHint アノテーションを宣言したツール/していないツールで、実際に承認結果が変わる実測ログ
  • readOnlyHint のデフォルト値が false(=書き込み扱い)であるという、見落としがちなMCP仕様の落とし穴

前提環境

  • OS: Linux(Ubuntu 24.04)
  • Node.js: v22.x
  • Codex CLI: @openai/codex v0.144.1(npm)
  • 認証: OPENAI_API_KEYcodex login --with-api-key で登録
  • 自作した最小限のstdio MCPサーバー(Node.js、外部依存なし)

TL;DR

  • writes モードは apps._default.default_tools_approval_mode = "writes"(または apps.<id>...)で設定する2--strict-config フラグでこのキーが有効な設定として認識されることを確認済み。
  • 自作MCPサーバーに readOnlyHint: true を明記したツールは、writesモードで 確認なしに実行された
  • 同じサーバーの、アノテーションを一切付けていないツールは、writesモードでも 承認待ちのまま codex exec にキャンセルされた(非対話実行のため承認者がおらず自動失敗)。
  • さらに検証すると、この「宣言なしツールが失敗する」挙動は writes モードを指定してもしなくても まったく同じ だった。つまりwritesモードの恩恵は、サーバー側が readOnlyHint を明示している場合にしか発生しない。
  • MCP仕様上 readOnlyHint のデフォルト値は false(=書き込みとみなす)3。自作・サードパーティ問わず、多くのMCPサーバーはこのアノテーションを付け忘れているため、「writesモードにすれば承認が減る」と期待して導入すると裏切られるケースが多いはずだ。

検証環境のセットアップ

まずCLIをインストールし、APIキーでログインする。

npm install -g @openai/codex
codex --version
# => codex-cli 0.144.1

codex login --with-api-key <<< "$OPENAI_API_KEY"
codex login status
# => Logged in using an API key - sk-proj-***

次に、承認モードの検証専用に最小限のstdio MCPサーバーを自作する。ツールを2つ用意した。

  • read_hinted: annotations: { readOnlyHint: true } を明記
  • read_no_hint: アノテーションを一切付けない(MCP仕様のデフォルトに委ねる)
// mini-mcp-server.js(抜粋)
if (method === 'tools/list') {
  send({ jsonrpc: '2.0', id, result: { tools: [
    {
      name: 'read_no_hint',
      description: 'Returns a fixed string. No annotations declared.',
      inputSchema: { type: 'object', properties: {} }
    },
    {
      name: 'read_hinted',
      description: 'Returns a fixed string. Explicitly read-only.',
      inputSchema: { type: 'object', properties: {} },
      annotations: { readOnlyHint: true, title: 'Read Hinted' }
    }
  ]}});
}

どちらのツールも実際にやることは同じ(固定文字列を返すだけ)で、実害のある書き込みは一切行わない。違いは MCPサーバーがCodexにそれを申告しているかどうか だけだ。これをグローバル設定として登録する。

codex mcp add minitest -- node /tmp/claude/codex-test/mini-mcp-server.js
codex mcp get minitest
# => minitest
#      enabled: true
#      transport: stdio
#      command: node
#      args: /tmp/claude/codex-test/mini-mcp-server.js

writes モードの設定キーを確認する

公式のconfig reference2には apps._default.default_tools_approval_mode(全アプリ共通)と apps.<id>.default_tools_approval_mode(アプリ個別)、apps.<id>.tools.<tool>.approval_mode(ツール個別)の3階層が載っている。値は auto | prompt | writes | approve の4種類だ。

このキーが実在するかは --strict-config(未知の設定キーがあるとエラーで止まる検証専用フラグ)で裏取りできる。

# 正しいキー: エラーにならず実行できる
codex exec --strict-config \
  -c apps._default.default_tools_approval_mode=\"writes\" \
  "respond with just OK, do not call any tools" \
  --skip-git-repo-check
# => OK(正常終了)

# 対照実験: 存在しないキーだとこうなる
codex exec --strict-config -c totally_bogus_key_xyz=\"writes\" "respond OK" --skip-git-repo-check
# => Error loading config.toml: unknown configuration field `totally_bogus_key_xyz` in -c/--config override

apps._default.default_tools_approval_mode はエラーにならず通った。対照実験で存在しないキーが即座にエラーになることも確認できたので、これは有効な設定パスだと判断してよい。

実測: readOnlyHintの有無で結果が変わるか

writes モードを指定した状態で、read_hinted(宣言あり)と read_no_hint(宣言なし)をそれぞれ1回ずつ呼ばせてみる。--json でイベントログを取得した。

宣言ありツール(read_hinted):

codex exec --json \
  -c apps._default.default_tools_approval_mode=\"writes\" \
  "Call the MCP tool named read_hinted exactly once, then report what it returned." \
  --skip-git-repo-check
{"type":"item.completed","item":{"id":"item_0","type":"mcp_tool_call","server":"minitest","tool":"read_hinted","result":{"content":[{"type":"text","text":"called read_hinted"}]},"error":null,"status":"completed"}}

問題なく completed している。プロンプトなしで即実行された。

宣言なしツール(read_no_hint):

codex exec --json \
  -c apps._default.default_tools_approval_mode=\"writes\" \
  "Call the MCP tool named read_no_hint exactly once, then report what it returned." \
  --skip-git-repo-check
{"type":"item.completed","item":{"id":"item_0","type":"mcp_tool_call","server":"minitest","tool":"read_no_hint","result":null,"error":{"message":"user cancelled MCP tool call"},"status":"failed"}}

status: "failed"、エラーは "user cancelled MCP tool call"。ツール自体は同じ「固定文字列を返すだけ」の処理なのに、アノテーションの有無だけで明暗が分かれた。codex exec は非対話実行(このセッションのデフォルト表示では approval: never)なので、承認が必要と判定された呼び出しはユーザー入力を待てず、その場でキャンセル扱いになる。

writesモードを外しても結果は同じだった

念のため、writes モードの指定自体を外した素の状態でも read_no_hint を試した。

codex exec --json \
  "Call the MCP tool named read_no_hint exactly once, then report what it returned." \
  --skip-git-repo-check
{"type":"item.completed","item":{"id":"item_0","type":"mcp_tool_call","server":"minitest","tool":"read_no_hint","result":null,"error":{"message":"user cancelled MCP tool call"}}}

結果は同一だった。つまり writes モードを設定しても、readOnlyHint を宣言していないツールに対しては 何も変わらない。「writesモードにすれば承認が減る」という期待は、サーバー側の実装が伴って初めて成立する条件付きの話だったことになる。

なぜこうなるのか

原因はCodex側の実装バグではなく、MCP仕様そのものの設計にある。MCPのツールアノテーション仕様では、readOnlyHint を含む全てのヒントに 保守的なデフォルト値 が定義されている3

アノテーション デフォルト値 意味
readOnlyHint false 宣言がなければ「書き込みする」とみなす
destructiveHint true 宣言がなければ「破壊的操作」とみなす
idempotentHint false 宣言がなければ「毎回副作用がある」とみなす

つまりMCP仕様は「性善説」ではなく「性悪説」で設計されている。ホスト(この場合はCodex)は、サーバーが明示的に安全だと申告しない限り、そのツールを潜在的な書き込み操作として扱う。これは安全側に倒した妥当な設計だが、裏を返せば 書いた本人が「これは読み取り専用ツールだ」と思っていても、annotations フィールドにそう書かなければCodexには伝わらない ということだ。

社内向けに雑に書いたMCPサーバーや、readOnlyHint が整備される前の古いサーバーは軒並みこの対象になる。writesモードを導入する前に、実際に繋いでいるMCPサーバーの tools/list レスポンスを確認し、annotations.readOnlyHint が付いているツールがどれだけあるかを見ておく方がよい。

まとめ

  • writes 承認モードは apps._default.default_tools_approval_mode = "writes" で有効化できる(--strict-config で存在確認済み)。
  • 効果があるのは、MCPサーバーが readOnlyHint: true を明示しているツールだけ。
  • 宣言していないツールは、writesモードの有無にかかわらず承認待ちになり、非対話実行(codex exec)では黙って失敗する。
  • 原因はMCP仕様の readOnlyHint デフォルト値が false(書き込みとみなす)であるため。自作MCPサーバーを使っている場合は、アノテーションの付与状況を先に棚卸しすることを勧める。

著者視点の発見ポイント

最初は「writesモードを設定すればCodexが賢く判定してくれる」くらいの感覚で試そうとしていた。ところが自作の最小サーバーで検証を始めると、同じ「固定文字列を返すだけ」の処理なのに一方は通り一方は失敗するという結果に直面し、「モードの実装が壊れているのでは」と疑った。だが writes を外した対照実験で同じ失敗が再現したことで、原因はCodex側ではなくMCP仕様の readOnlyHint デフォルト値にあると特定できた。公式のリリースノート一文だけを読んで「プロンプトが減る機能」と早合点していたら、実際に自分のMCPサーバー群でも同じ落とし穴を踏んでいたはずだ。設定キーの存在を --strict-config で裏取りしてから動作検証に進んだことで、「キーが間違っているのか」「挙動が仕様通りなのか」を切り分けられたのも今回の収穫だった。

参考リンク

  1. Release 0.144.0(openai/codex)(2026-07-09公開)

  2. Configuration Reference(OpenAI Developers) 2

  3. Tool Annotations as Risk Vocabulary(MCP Blog) 2

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?