ThreadsとXのアクセストークンをcronで更新する:fetch failedと本当の失効の見分け方

SNSへ代行投稿するサービスでは、アクセストークンを期限内に更新し続ける仕組みが欠かせません。私のサービス「sns-poster」では、cronで定期的に更新しています。この記事では、更新失敗の通知が来たときに「本当に失効したのか、一時的な通信障害なのか」を見分ける方法と、通知設計の考え方を書きます。

なぜcronで更新するのか

更新に失敗すると、次の投稿が失敗します。そのため、失敗の通知は必須です。

起きたこと:fetch failed のメール

ある日、次のようなメールが届きました。

[システム名] Xトークンのリフレッシュに失敗しました
エラー: fetch failed
refresh_tokenが無効化されている可能性があります。再認可してください。

文面どおりに再認可しようとしましたが、その前に原因を確認しました。結果は、cron実行時のDNSの一時的な失敗(EAI_AGAIN)で、トークン自体は有効でした。再認可は不要でした。

fetch failed とは何か

Node.jsの fetch(undici)が出す fetch failed は、相手から応答が返ってこなかったときの汎用的なエラーです。本当の理由は error.cause に入っています。

見え方意味対処
fetch failed(causeが ENOTFOUND / EAI_AGAIN)DNSが引けない一時的。リトライ
fetch failed(causeが ETIMEDOUT / UND_ERR_CONNECT_TIMEOUT)接続できない一時的。リトライ
fetch failed(causeが ECONNRESET)接続を切られた一時的。リトライ
HTTP 400 + invalid_grant など相手が明確に拒否したトークンが無効。再認可

見分け方の基本は、HTTPの応答が返ってきたかどうかです。応答が返っていれば、ステータスと本文に理由があります。返っていなければ、通信の問題です。

通知の設計

メールの「無効化されている可能性があります」という文面は、失敗の種類にかかわらず出る定型文でした。これが誤解のもとでした。次のように分けるのが妥当です。

  1. ネットワーク系のエラー: 一定間隔で数回リトライする。連続して失敗したときだけ通知する
  2. **明確な拒否(invalid_grant など)**: 待っても直らないので、即時に通知して再認可を案内する
  3. 通知の文面: 断定しない。「通信エラーの可能性」と「再認可が必要な可能性」を、失敗の種類に応じて書き分ける

ログに残すもの

原因の切り分けには、次の3つをログに残します。

fetch failed の一言だけでは、後から調べられません。

更新の「ローテーション」にも注意

Xのリフレッシュトークンは、更新のたびに新しいものに置き換わります。次の場合は、本当に invalid_grant になります。

通信障害と見分けるには、更新の成功と保存を一続きの処理として扱い、同じ連携に対する更新が重ならないようにしておく必要があります。

まとめ

sns-posterは、X・Threads・Instagram・LinkedIn・Facebookへ予約投稿できるサービスです。詳しくは機能ページをご覧ください。