zudo-slack-wisdom
GitHub リポジトリ

検索したい単語を入力

いつでも検索バーを開ける

その場での更新

chat.update がもつ絶対的な読み取り専用保証、ピン留めダッシュボードのパターン、Table ブロックの制限

更新できるのは投稿したアプリだけ

chat.update は既存メッセージのテキストとブロックを編集する。Slack 自身のドキュメントに よれば「更新できるのは認証済みユーザーが投稿したメッセージのみ」であり (chat.update)、この「認証済み ユーザー」とはトークンの主体を指す — 必ずしもアプリのことではない。bot トークンの場合は bot 自身がそれにあたる。あるメッセージを編集できるのは、それを最初に投稿した bot だけだ。 設定すべき ACL もなければ、設定を誤りうる共有ダイアログもなく、別のアプリや人間にメッセージ 本文の編集権限を与える管理者の上書きも存在しない。ただしユーザートークンは人間として 認証されるため、それを使って呼んだ chat.update は、その人間が書いたメッセージであれば 何でも編集できる — 別のアプリがその人になり代わって投稿したもの(Slack コネクトや ワークフロー連携など)も含めてだ。以下で述べる絶対的かつ設定不要の読み取り専用保証は、この ページが扱う bot トークンによるダッシュボードのパターンで成り立つものであって、ユーザー トークンを使う連携が自動的に得られるものではない。

通知なし、「編集済み」表示なし — ブロックを使う場合

メッセージをその場で編集してもチャンネルへの通知は発生しない。更新のたびにチャンネルを 騒がせたくないステータスボードにとっては、これが正しい挙動だ。メッセージの中身が素の text ではなく Block Kit の blocks である場合、Slack は「(編集済み)」の表示も出さない。つまり chat.update で更新された Block Kit メッセージは、何度更新されても見た目上は最初に投稿された ときとまったく同じに読める。

ピン留めダッシュボードのパターン

以上の組み合わせ — bot だけが書き込めること、静かに更新されること、「ここが変わった」という 視覚的なマーカーが出ないこと — によって、chat.update は Slack 内ステータスボードを組み立てる ときの既定の部品になる。

  1. 最初のブロック本文とともに chat.postMessage を 1 回だけ実行する。

  2. 得られたメッセージを pins.add して、チャンネルのピン留めアイテムのパネルから辿れるように する。

  3. cron(あるいは任意のトリガー)で、同じ channel + ts に対して新しくレンダリングした blocks ペイロードを chat.update する。

これは更新 1 回あたりちょうど API 呼び出し 1 回で済み、channel/ts のペア以外に状態を持つ 必要がなく、追加設定ゼロで前述の読み取り専用保証を引き継ぐ。なおピン留めしても、メッセージが チャンネルのタイムライン上部に固定されるわけではない。ピン留めアイテムの一覧に追加される だけなので、新しいメッセージが届けばダッシュボードは可視範囲の履歴から流れていく。ピンは、 スクロール位置にかかわらず読み手がそこへ戻れるようにするためのものだ。ピンはチャンネルの メンバーなら誰でも外せるが、ピンを外してもメッセージが削除されるわけではない。ダッシュボード 自体は残り、ピン留めアイテムの一覧から外れるだけだ。

bot が書き込める表示面のなかでの既定の選択肢

bot がダッシュボードを書き込める他の面 — Slack リスト、キャンバス、App Home タブ、外部ページ へのチャンネルブックマーク — と比べると、ピン留めした chat.update メッセージは最も低コスト でありながら最も強い読み取り専用保証をもつ選択肢だ。その代わりに本格的なレイアウトモードは 諦めることになる(カンバンボードもなければ、行ごとのスレッドもない)。より重い面に手を伸ばす のは、チームが実際にそうした機能を求めてからでいい。

多数のメッセージをまとめて整合させる

上記のパターンは、1 つの channel/ts に住み続ける単一のダッシュボードメッセージを前提と している。レコードごと、チームごと、進行中の項目ごとといった具合に、すでに投稿した多数の メッセージを維持する bot には、単一の固定ターゲットではなく整合(reconciliation)パスが 必要になる。

各メッセージの channel/ts と並べて、バージョンをソルトに含めたコンテンツハッシュを 保存する([renderVersion, parentPayload, replyPayload] に対する SHA-256 ダイジェストがうまく 機能する)。整合パスはそれぞれ、現在のデータからペイロードを再構築してハッシュを計算し、 保存済みの値と比較する。chat.update はハッシュが一致しない場合にのみ呼び出されるため、 変化していないレコードの API 呼び出しコストはゼロになる。

新しいハッシュを保存するのは、その行に対するすべての Slack 呼び出しが成功したあとに 限る — ハッシュは「更新を試みた」という記録ではなく、書き込みが完了したことのコミット マーカーだからだ。親メッセージの更新は成功したが、それに依存する返信の更新が例外を投げた 場合、保存済みのハッシュは古い値のまま残る。そのため、その行は依然として「古い」状態として 読み取られ、実際には Slack の状態と一致していないのに完了扱いされることなく、次のパスで 再試行される。

renderVersion というソルトを上げることが、テンプレートの刷新によってすでに投稿された すべてのメッセージを自ら書き直させる方法になる。元データが変わっていなくても、すべての行で ハッシュが変化するため、次のパスはフリート全体を「古い」とみなし、各メッセージを 1 回ずつ 書き直す。

1 回のパスで実行する更新数には上限を設け、候補の処理順も意図的に決める — まだ一度も ハッシュ化されていない行(Slack にまだ一切存在しない新規レコード)を先に、続いて新しい行を 処理する。こうすることで、大きな滞留分は 1 回のパスでチャンネルあたりの投稿上限を突破する のではなく、複数の cron ティックにわたって徐々に消化されていく(レート制限 を参照)。

投稿と更新でペイロードビルダーを共有する

chat.postMessagechat.update の両方で Block Kit ペイロードを組み立てる関数を 1 つに 共有すると、2 つの呼び出し箇所が食い違っていくのを防げる。ただし一部のフィールドは 投稿専用であり、chat.update に送る前に取り除く必要がある: thread_tsunfurl_linksunfurl_media だ。スレッドは投稿時点で固定される — chat.update にはそもそも thread_ts パラメータが存在しないし、更新呼び出しに unfurl フラグを渡してもよくて no-op にしかならない。 ビルダーの出力は、更新パス向けにストリップ処理が必要なものとして扱い、そのまま使い回せる ものとは考えないこと。

bot のスレッド返信をあとから再発見する必要が生じうる場合 — 返信の ts を永続化していな かった、あるいはそれを追跡していた行を失ってしまった場合 — そうした返信には安定した、機械的に 認識できるテキストプレフィックス(例えば返信テキストの先頭に置く固定のマーカー文字列)を 付けておく。あとのパスでは、親の ts に対して conversations.replies を呼び出し、結果を bot 自身の bot_id とそのプレフィックスの一致でスキャンすることで自分の返信を再発見でき、 一度も保存していなかった ts を回復して冪等な chat.update の再開に使える。

Table ブロックの制限

Block Kit の Table ブロックは、chat.update で更新されるメッセージの内側で使える構造化 レイアウトのなかで最も表現力が高い。2026-08-06 時点での一次リファレンス (Table block) によれば以下の とおり。

制限
メッセージあたりのテーブル数1 — 同一メッセージ内の 2 つ目の Table ブロックは only_one_table_allowed で失敗する
行数最大 100
行あたりの列数1 行の配列につき最大 20 セル
テーブルあたりの文字数そのテーブル内の全セル合計で 10,000
メッセージあたりの文字数メッセージ内の全テーブルセルを合算して 10,000
セルの型rich_textraw_textraw_number
block_id最大 255 文字

100 行や 10,000 文字を超えるテーブルコンテンツが必要になったダッシュボードは、この面には 収まりきっていない。データをページングするか、要約するか、より大きな構造化データ向けに作られた 面(Slack リスト。リストのセクションを参照)に移すことになる。

Revision History

作成更新