select カラム — ラベルではなく値
slackLists.items.update がラベルを拒否する理由と、select カラムのスラッグ設計
概要
select カラム(単一選択・複数選択)は、ステータスボードにおける最も一般的な書き込み対象だ — たとえばカンバン式の「todo / doing / done」カラムがそれにあたる。書き込みが受理されるかどうかは正しい文字列を送れるかにかかっているが、その文字列は人間がチップ上で目にするものではない。
select 配列が運ぶのは値であって、ラベルではない
select 配列が運ぶのはカラムスキーマの options.choices[].value — 機械可読なスラッグ — であって、人間が読むチップの文字列である choices[].label ではない。ラベルを書き込むと invalid_option_id が返る。
Slack のドキュメントは散文で select の値を "an array of List encoded option IDs" と説明し、例では OptHIGH123 形式のプレースホルダーを使っているため、choices[] に別途 id キーがあるかのように読める。だが実際にはない:API のどこを見ても、choice オブジェクトに id キーは存在しない。 choice は確認したすべての面で厳密に {value, label, color} だ — slackLists.create のリクエストとレスポンス、items.info のスキーマ、Node SDK の SlackListsSchemaColumnChoice、そして Java SDK の ListColumnOptions.Choice。value フィールドこそがオプション ID である。 ドキュメント自身の実例がそれを証明している:カラム Col018AL7649G は {"value": "in_progress", "label": "In Progress"} を持ち、そのカラムに対する update のサンプルは select: ["in_progress"] を書き込んでいる。
ローカライズされたラベルの下に ASCII スラッグを置くパターン
value と label は独立したフィールドなので、select のオプションは value に安定した ASCII スラッグを持たせつつ、label には英語以外を含む任意の人間向けテキストを置ける — 書き込み経路はラベルにいっさい触れない。
これが実際に機能しているパターンであることを示す本番の証拠:
navikt/saape-slackapp はノルウェー語のチップを
{value: 'pending', label: 'Avventer'}, {value: 'in_progress', label: 'Pågår'}, ...と宣言し、select: [listStatus]で切り替えている — 値は ASCII のままで、ローカライズされるのはラベルだけだ。n8n コミュニティノードの README は "Select columns accept and return the option value (e.g.
day_1,OptXXXXXX)." と述べている。SirMaiquis/deno-mr-poc は label→value のリゾルバ(
choices.find(c => c.label === ...)→option.valueを送る)を実装している — ラベルがそのまま通るなら存在しないはずのコードだ。
退化したケース
value === label で定義されたカラムであれば、ラベルの文字列を送ってもたまたま動く — その場合はそれが値でもあるからだ。これに無自覚に依存しないこと。value と label は独立に選び、あとからラベルを変更(翻訳や言い回しの調整)しても書き込みが壊れないようにしておく。
bot がリストを作るパターン
slackLists.create で bot 自身にリストを作らせ、すべてのオプションの value を最初に自分で決めること。これは 2 つの問題を同時に解決する:
スラッグの制御。 任意の表示ラベルの下に中立的な ASCII の値(
todo/doing/done)を選べるため、ラベル↔ID の対応づけ問題がまるごと消える — スラッグは Worker のコード内のコンパイル時定数になる。アクセス。 bot が自分で作成して いない リストへの書き込みアクセスを、API だけでどう得るのかは、本当にドキュメント化されていない:
slackLists.access.setはchannel_idsとuser_idsしか受け取らず、app_idsや bot 用の引数はどこにも存在しない。それでいてitems.updateは明確にアクセスを強制する(access_denied、no_permission、permission_denied)。リストを作ることはこの API 上の穴を丸ごと回避する:Slack のドキュメントは新しいリストを「acting user が所有する」と説明しており、bot トークンでのslackLists.create呼び出しにおける acting user は bot 自身である。すでに運用担当者がリストを所有している場合は、access.setではなく手作業の共有 UI による編集権付与が本番で実際に使われている経路だ — 両方の経路を並べて見るにはワンウェイミラーパターンを参照し、どちらの経路で書き込みを有効にする場合も、その前に実際のライブプローブを必須とすること(有効化ゲートのプローブを参照)。
未検証
作成者が自動的に書き込みアクセスを保持すること — つまり bot 自身の xoxb トークンが slackLists.create の呼び出し後にそのリストのオーナーとして扱われること — は推論であって確認済みではない。Slack のドキュメントは新しいリストが「acting user が所有する」と述べてはいるが、bot トークンでの呼び出しについて「bot ユーザー」だと明言してはおらず、実際のワークスペースに対してこれを決着させるはずだった検証スパイクはスキップされている(テスト用トークンが用意できなかった)。設計の前提にする前に、たとえば access_level: "owner" を指定した access.set のようなオーナー限定の操作を bot に試させて、所有権を経験的に確認すること。
すでに人間がリストを作ってしまっている場合、それを発見するための slackLists.info も slackLists.list も存在しない。発見手段は slackLists.items.info(lists:read が必要)であり、これは list.list_metadata.schema[] を含むリストオブジェクト全体 — つまりすべての select カラムとその id および options.choices[] — を返す。一度手作業で呼び出し、そのうえで値をハードコードすること。
絵文字ラベルの照合:Unicode と :shortcode:
Slack の UI で作成された select オプションは、label に絵文字を含められる — たとえば管理者がチップの表示テキストとして「👍 Approved」と入力する場合だ。API は入力した通りの形で返ってくることを保証しない:同じラベルが、あるときは Unicode 文字(👍 Approved)として、また別のときはコロン付きのショートコード形式(:+1: Approved)として往復することがある。これは特にラベルベースの解決 — 既知のラベル文字列を options.choices[].label と照合して対応する value を見つけること — にとって重要になる。これは、人間が手作業で作ったリストで利用できる唯一の経路だ(前掲のbot がリストを作るパターンを参照):キーにできる id は存在せず、value のスラッグが何か予測可能なものだとも仮定できない。
リゾルバを書いたときにたまたま見かけた表現だけでなく、両方の表現を照合すること — 保存済みのラベルと比較する前に、Unicode の絵文字とそのショートコードのエイリアスを正規化する(あるいは両方をチェックする)。あるカラムの options.choices[] に、Unicode 形式とショートコード形式が — 読み取りのたびに表示が変わるだけの 1 つのオプションとしてではなく — 別々のオプションとして存在してしまっている場合は、それを設定上のエラーとして扱うこと:両方のオプションが同じステータスを意味するために存在している以上、それはコードで解決できる照合の問題ではなく、曖昧なスキーマの問題だ。
これはこのページの中心的な助言を裏づけるものでもある:最初に解決できた時点から、label ではなく不透明な value を永続化すること。ラベルマッチングは、人間が作ったリストを自分の保存済みスラッグへ橋渡しするためにセットアップ時に一度だけ走らせるべきもので、書き込みのたびに走らせるものではない。
単一選択と複数選択はまったく同じ形で書き込む
multi_select という別のセルキーは存在しない。単一選択のカラムも複数選択のカラムも、同じ select: [...] 配列を通して書き込む — いくつの値が受理されるかを決めるのは書き込みの形ではなく、カラムの options.format(single_select か multi_select か)だ。
未検証
single_select 形式のカラムに 2 つの値(select: ["a", "b"])を送ったときの挙動はドキュメント上未定義だ — エラーになるのか、片方の値だけが残るのか、黙って受理されるのか、いずれも未テストである。
既存セルに対する置換か追加か
未検証
すでに "A" を保持しているセルに select: ["B"] を書き込んだとき、それが追加ではなく 置換 になると述べた Slack のドキュメントページは存在しない。single_select 形式のカラムであれば置換だけが筋の通る意味論であり、本番のアプリもそれに依存しているが、これは推論であってドキュメント化された契約ではない。追加と置換で効果が本当に分かれる multi_select 形式のカラムでは、これが二重に効いてくる。1 つの行に対して items.update を 2 回連続で呼び(["A"] を書いてから ["B"] を書く)、items.info で読み返して厳密に ["B"] であることを表明してから依存すること。2 つの形式は挙動が異なりうるので、multi_select 形式のカラムに対しても別途繰り返すこと。
select: [] による選択の解除
未検証
select: [] が値の入った select セルをクリアするかどうかはドキュメント化されていない。少なくとも 1 つの本番コードベース Kero46/slack-review-reminder は、これに依存する代わりに空のセルを update のペイロードから意図的に除外している。設計上「ステータスなし」という明示的な状態が必要なら、実際の API に対して確認が取れるまでは空配列によるクリアに頼らず、実在するオプションの値(たとえば "none")としてモデル化すること。
関連
書き込みの契約全体(list_id + cells[]、型付きの値キー、バッチ化)はリストアイテムへの書き込みを参照。リスト作成後にオプションの集合が実質的に閉じてしまう理由はスキーマの可変性を参照。不正なスラッグが生むエラーについてはエラーリファレンスを参照。