ヘルプ
トラブルシューティング
Calendarpit の起動、OAuth、Calendar API、同期、watch、管理 API でよく起きるエラーの原因と対処をまとめています。
リクエストが失敗すると、Calendarpit は明示的なステータスコードとエラーメッセージを返します。未対応のフィールドやパラメータを黙って無視することはありません。
原因を調べるときは、管理画面の API history(http://127.0.0.1:4173/history)を開きます。
- 直近 200 件の Calendar API と OAuth のやり取りを確認できます。
- 各エントリには HTTP メソッド、パス、ステータスコード、ヘッダー、マスク済みのリクエストボディとレスポンスボディが記録されます。
- Errors トグルを使うと、ステータスが
400以上のレスポンスだけを表示できます。
このページの各項目は、同じ形で書かれています。正確なエラー出力(ある場合)、考えられる原因、対処の順です。
エラーレスポンスの形式
Google API のエラー形式
Calendar API のルート(/calendar/v3/*)、/userinfo、管理 API のルート(/__calendarpit/*)は、Google API と同じ形式でエラーを返します。
{
"error": {
"code": 404,
"message": "Event not found.",
"errors": [
{
"domain": "calendarpit",
"reason": "notFound",
"message": "Event not found."
}
]
}
}- トップレベルのエラーでは、
domainは常に"calendarpit"です。freebusy.queryレスポンスの中のカレンダーごとのエラーは"domain": "global"になります。 401レスポンスにはWWW-Authenticate: Bearerヘッダーが付きます。- より具体的な reason がない
400の検証エラーは"reason": "invalidParameter"になります。
OAuth のエラー形式
OAuth のルート(/oauth/* と /oauth2/*)は OAuth のエラー形式を返します。ほとんどのエラーは HTTP 400 ですが、次は例外です。
- 未実装のパスは
501を返します。 - 保留中の同意リクエストが多すぎる場合は
429を返します。 - 別オリジンから同意画面を送信した場合は
403を返します。
{
"error": "invalid_grant",
"error_description": "Invalid, expired, or mismatched authorization code."
}GET /oauth/authorize の検証エラーは、HTTP 400 の JSON としてブラウザに直接返ります。Calendarpit は不正な認可リクエストを redirect_uri にリダイレクトしません。
Calendarpit の起動
CLI で起動に失敗すると、stderr に Calendarpit: <message> を出力し、終了コード 1 で終了します。Docker の出力は docker compose logs calendarpit で確認できます。
ポートが使用中
- エラー:
Calendarpit: Failed to start server. Is port 4173 in use? - 原因: 別のプロセスがポート
4173を使っています。 - 対処: 別のプロセスを停止するか、
--port <n>でポートを変更します。--port 0の場合は、実際のAdminURL を確認します。Docker ではホスト側を127.0.0.1:4174:4173などに変更し、ポート4174にアクセスします。
データベースが使用中
- エラー:
Calendarpit: Database is already in use: <path>. Use a separate path or :memory:. - 原因: 別のプロセスが同じ SQLite ファイルをロックしています。
- 対処: 別のプロセスを停止するか、プロセスごとに異なる
--databaseのパスまたは:memory:を使います。ストレージと再起動を参照してください。
Fixture ファイルが見つからない
- エラー:
Calendarpit: Fixture file not found: <path>. … - 原因: Fixture のパスが作業ディレクトリ内、またはコンテナ内に存在しません。
- 対処: 正しい絶対パスを指定します。Docker ではファイルをマウントし、コンテナ内のパスを渡します。Fixtureを参照してください。
Fixture のパスがディレクトリになっている
- エラー:
Calendarpit: Fixture path is a directory, not a file: <path> - 原因: コンテナで、バインドマウントの元パスがコンテナランタイムの VM と共有されていないため、Docker がマウント先に空のディレクトリを作成しました。Colima でホームディレクトリ外のパスを指定すると発生します。ホスト上に存在しないパスを指定した場合も同じ結果になります。
- 対処: ホストのディレクトリをランタイムと共有する(Colima では
colima.yamlのmountsに追加して再起動する)か、起動前にdocker cpでファイルをコンテナにコピーします。ホストのディレクトリをコンテナランタイムと共有するを参照してください。
Fixture ファイルを読み取れない・JSON として不正
- エラー:
Calendarpit: Fixture file is not readable: <path>. …またはCalendarpit: Fixture file is not valid JSON: <path> (<reason>) - 原因: コンテナの非 root ユーザーがファイルを読み取れないか、ファイルが正しい JSON ではありません。
- 対処: ほかのユーザーも読み取れるようにする(例: モード
644)か、JSON の構文を修正します。
Fixture の検証に失敗する
- エラー:
Calendarpit: Each user with calendars must have exactly one primary calendar. - 原因: 起動時 Fixture の検証に失敗しました。
- 対処: カレンダーを持つ各ユーザーに、
"primary": trueのカレンダーを必ず 1 つ設定します。Fixture のルールを参照してください。
再起動しても Fixture の変更が反映されない
- 原因: 起動時 Fixture は新しいデータベースだけに適用されます。既存のデータとリセット基準は保持されます。
- 対処:
- 現在のデータを置き換えるには、Settings → Test data または
POST /__calendarpit/fixturesで読み込みます。 - リセット基準も変えるには、新しいデータベースのパスか
:memory:で起動します。
- 現在のデータを置き換えるには、Settings → Test data または
起動方法を変えたらデータが見えない
- 原因: 次のいずれかです。
- 作業ディレクトリが変わった。
- 別のデータベースのパスやボリュームを使った。
- 前回は
:memory:を使っていた。
- 対処: 起動時の
Data <path>と Settings → Test data を確認します。同じ絶対パスまたは Docker ボリュームを使ってください。
Docker でブラウザ、バックエンド、Webhook の受信先に接続できない
- 原因: ループバックはリクエスト元のコンテナ自身を指します。また、ブラウザは Compose のサービス名を解決できません。
- 対処: ブラウザではホスト側に公開した URL、コンテナ間ではサービス名を使います。Webhook の受信先には
CALENDARPIT_WATCH_HOSTSも設定します。コンテナの接続先を参照してください。
CALENDARPIT_* の値が反映されない
- 原因: CLI フラグで上書きしているか、ホストで設定した環境変数を Docker に渡していません。
- 対処: 上書きするフラグを外すか、
docker run -eまたは Compose のenvironmentで渡します。コンテナのポートを変更する場合は、ポートのマッピングとヘルスチェック用のCALENDARPIT_PORTも揃えてください。
オプションの値が不正、未知のオプションやコマンド
- 原因: 不正なフラグ、空の値、未対応のサブコマンドを指定しています。
- 対処:
calendarpit --helpで確認します。サブコマンドはserveのみです。ポートは0から65535の整数で指定します。
OAuth
本物の Google の同意画面が表示される
- 原因: アプリの認可 URL がまだ Google(
accounts.google.com)を指しています。 - 対処: 認可 URL を
http://127.0.0.1:4173/oauth/authorizeに設定します。詳しくはアプリを接続するを参照してください。
トークンの交換や更新で Google に接続してしまう
- 原因: OAuth クライアントがまだ Google のトークンエンドポイントを使っています。Calendar API のエンドポイントを設定しても、OAuth の呼び出し先は変わりません。
- 対処: トークン URL を
http://127.0.0.1:4173/oauth/tokenに上書きします。/oauth/revokeと/userinfoを使う場合は、それらも上書きします。詳しくはローカル OAuthを参照してください。
Google のデフォルトの OAuth パスで 501 になる
- エラー:
/oauth2/...や/oauth/tokeninfoで501 unsupportedFeature - 原因: SDK が Google のデフォルトの OAuth パス(
/oauth2/v4/token、/oauth2/v3/userinfo、/oauth/tokeninfo)を呼んでいます。 - 対処: エンドポイントを
/oauth/authorize、/oauth/token、/oauth/revoke、/userinfoに明示的に上書きします。
登録されていないリダイレクト URI
- エラー:
invalid_request:Unregistered redirect URI. - 原因:
redirect_uriがclients[].redirectUrisのどのエントリとも完全には一致していません。localhostと127.0.0.1の違い、ポート、パス、末尾のスラッシュも区別されます。SAMPLE_PORTを4175から変えた場合も、組み込みのsample-appクライアントと一致しなくなります。
- 対処:
redirect_uriの文字列をそのまま Fixture のclients[].redirectUrisにコピーします。
未知のクライアント
- エラー:
invalid_client:Unknown client. - 原因: 次のいずれかです。
client_idが Fixture に登録されていない。POST /oauth/tokenをapplication/x-www-form-urlencodedではなく JSON ボディで送った。クライアントが HTTP Basic で認証している場合は、代わりにunsupported_grant_typeが返ります。
- 対処: クライアントを Fixture に登録します。トークンリクエストは
application/x-www-form-urlencodedで送ります。
クライアントシークレットが不正
- エラー:
invalid_client:Invalid client secret. - 原因:
client_secretがないか、間違っています。Calendarpit は PKCE フローも含め、すべてのトークンリクエストでクライアントシークレットを必須にしています。 - 対処: 正しい
client_secretを、フォームボディか HTTP Basic 認証で送ります。
認可コードが不正
- エラー:
invalid_grant:Invalid, expired, or mismatched authorization code. - 原因: 次のいずれかです。
- 認可コードが発行から 60 秒を過ぎている。
- 認可コードがすでに 1 回使われている。
redirect_uriがないか、/oauth/authorizeのときと違う。- クライアントが違う。
- 接続が取り消された。
- サーバーが再起動した。
- 対処: 60 秒以内に 1 回だけ交換します。
/oauth/authorizeで使ったものと同じredirect_uriとクライアントを使います。
PKCE の verifier が不正
- エラー:
invalid_grant:Invalid PKCE verifier. - 原因:
code_verifierがS256のcode_challengeと一致しないか、[A-Za-z0-9._~-]の 43〜128 文字になっていません。 - 対処:
S256のチャレンジを計算したときの元の verifier 文字列を送ります。
リフレッシュトークンが不正
- エラー:
invalid_grant:Invalid refresh token. - 原因: 次のいずれかです。
- 接続が取り消された。
- ユーザーが削除された。
- ワークスペースがリセットされたか、Fixture が読み込まれた。
- インメモリのサーバーが再起動した。
- 別のクライアントのトークンを使った。
- 対処: 認可フローをやり直して、新しい接続を作ります。
コールバックで state がない、または一致しない
- 原因: 次のいずれかです。
/oauth/authorizeでstateを省略した。stateは送ったときだけそのまま返ります。- アプリを
localhostで開始し、コールバックを127.0.0.1で受け取った。stateを保存した Cookie は、ホスト名をまたいで共有されません。
- 対処:
/oauth/authorizeにstateを渡します。アプリとredirect_uriでは同じホスト名(127.0.0.1かlocalhost)を使います。
未対応または未指定のスコープ
- エラー:
invalid_scope:Unsupported or missing scope. - 原因:
scopeが空か、未対応のスコープ(calendar.events.ownedや Drive のスコープなど)を含んでいます。 - 対処: 対応しているスコープだけを要求します。
更新時にスコープを変更した
- エラー:
invalid_scope:Refresh scope changes are unsupported. - 原因:
refresh_tokenリクエストにscopeパラメータが含まれており、最初に付与されたときの文字列と完全には一致していません。スコープの順序も区別されます。 - 対処: トークンを更新するときは
scopeを省略します。
トークンレスポンスに refresh_token がない
- 原因: 次のいずれかです。
/oauth/authorizeでaccess_type=offlineを送っていない。refresh_tokenグラントのレスポンスである。更新のレスポンスは新しいaccess_tokenだけを返し、元のリフレッシュトークンは有効なままです。
- 対処:
/oauth/authorizeにaccess_type=offlineを追加し、最初に受け取ったrefresh_tokenを保存しておきます。
未対応の認可パラメータ
- エラー:
/oauth/authorizeでunsupported_feature - 原因: リクエストに次のいずれかが含まれています。
prompt=noneinclude_granted_scopes=truenonce、login_hint、hd、enable_granular_consentなどの未対応のパラメータ
- 対処: 未対応のパラメータを外します。対話的な同意(
prompt=consentかprompt=select_account)を使います。
PKCE のチャレンジが不正
- エラー:
invalid_request:PKCE requires S256.またはMissing PKCE challenge. - 原因: 次のいずれかです。
code_challenge_method=S256なしでcode_challengeを送った(plainの場合も含む)。code_challengeなしでcode_challenge_methodを送った。code_challengeが 43 文字の base64url になっていない。
- 対処:
code_challenge_method=S256と、43 文字の base64url のチャレンジを使います。
同意リクエストの期限切れ、または使用済み
- エラー:
invalid_request:Consent request expired or already used. - 原因: 次のいずれかです。
- 同意画面を 5 分を超えて開いたままにした。
- 同意画面を 2 回送信した。
- 送信前にサーバーが再起動した。
- 対処:
/oauth/authorizeから新しい認可リクエストを始めます。
保留中の同意リクエストが多すぎる
- エラー:
429 rateLimitExceeded:Too many pending consent requests. - 原因: 保留中の同意リクエストがすでに 1,000 件あります。各リクエストは 5 分で期限切れになります。通常はテスト内のループが原因です。
- 対処: 同意リクエストを完了させるか、新しく開くのをやめます。またはワークスペースをリセットします。
Calendar API と SDK
アクセストークンが不正または期限切れ
- エラー:
401 authError:Invalid or expired access token. - 原因: 次のいずれかです。
Authorization: Bearerヘッダーがないか、形式が不正。- アクセストークンの期限切れ(有効期間 1 時間)。
- 接続の取り消し。
- ワークスペースのリセット、または Fixture の読み込み。
- インメモリのサーバーの再起動。
- 本物の Google のトークンの送信。
- 対処:
/oauth/tokenでトークンを更新します。リセット、Fixture の読み込み、取り消しの後は、認可をやり直します。
スコープが足りない
- エラー:
403 insufficientPermissions:Insufficient scope. - 原因: トークンに必要なスコープがありません。たとえば次の場合です。
.readonlyのスコープでイベントを書き込んだ。calendar.eventsだけでcalendarList.listを呼んだ。openid、email、profileのいずれもなしで/userinfoを呼んだ。
- 対処: エンドポイントに必要なスコープを要求して、認可をやり直します。詳しくはローカル OAuthを参照してください。
未対応の API エンドポイントまたはメソッド
- エラー:
501 unsupportedFeature:Unsupported API endpoint or method.。/calendar/v3/calendar/v3/...のようにパスが重複している場合も含みます。 - 原因: 次のいずれかです。
- 未実装の Google のエンドポイント(
acl、settings、colors、calendars.insert、events.move)を呼んだ。 - API のパスに
HEADを送った。一部のパスは、下記のとおりHEADに別のエラーを返します。 /users/me/calendarList/{calendarId}にPOSTを送った。- ベース URL の設定ミスで、SDK が
/calendar/v3を重複させた。
- 未実装の Google のエンドポイント(
- 対処: Calendar API の互換性を確認します。SDK のベース URL は下の表のとおりに設定します。詳しくはSDK の使用例を参照してください。
次のパスでは、HEAD に別のエラーを返します。
- イベントのパスは
405 methodNotAllowedを返します。 /calendars/{calendarId}はUnsupported calendar operation.を返します。/users/me/calendarList/{calendarId}の既存エントリはUnsupported calendar list operation.を返します。
| SDK | ベース URL の設定 |
|---|---|
JS/TS(@googleapis/calendar) | rootUrl を http://127.0.0.1:4173/ にします。 |
| Kotlin | rootUrl をオリジンに、servicePath を calendar/v3/ にします。 |
PHP(google/apiclient) | $rootUrl に http://127.0.0.1:4173/ を渡します。 |
| Python、Go、C# | /calendar/v3/ を含めます。 |
未対応のカレンダー操作またはカレンダーリスト操作
- エラー:
501 unsupportedFeature:Unsupported calendar operation.またはUnsupported calendar list operation. - 原因: 次のいずれかです。
/calendar/v3/calendars/{calendarId}にPUT、PATCH、DELETE、HEADを送った。/calendar/v3/users/me/calendarList/{calendarId}の既存エントリに、HEADやOPTIONSなどの未対応のメソッドを送った。そこへのPOSTは、代わりにUnsupported API endpoint or method.を返します。
- 対処: カレンダーは管理画面か
/__calendarpit/calendarsで管理します。ユーザーごとの表示設定にはcalendarList.patchを使います。詳しくはテスト制御 APIを参照してください。
カレンダーが見つからない
- エラー:
404 notFound:Calendar not found. - 原因: 次のいずれかです。
- 存在しないカレンダー ID。
- 別のテストユーザーが所有するカレンダー。
- カレンダーを持たないユーザーでの
primary。 calendarList.deleteでユーザーのリストから外したカレンダー。これはカレンダーリストのメソッドの場合のみです。
- 対処: 認証済みユーザーが所有するカレンダー ID を使うか、そのカレンダーの所有者として認可します。リストから外したエントリは
calendarList.insertで戻せます。
イベントが見つからない
- エラー:
404 notFound:Event not found. - 原因: 次のいずれかです。
- そのカレンダーに存在しないイベント ID。
POST .../events/importやPOST .../events/quickAdd(textなし)を呼んだ。importやquickAddがイベント ID として扱われます。
- 対処: イベント ID を確認します。
importやquickAddの代わりにevents.insertを使います。
未対応のイベントメソッド
- エラー:
405 methodNotAllowed:Unsupported event method.またはUnsupported instances operation. - 原因: イベントのルートに対して使えない HTTP メソッドを送りました。たとえば次の場合です。
HEAD/eventsへのDELETE- 既存の
/events/{eventId}へのPOST /instancesへの書き込み
- 対処: 対応している HTTP メソッドとパスを使います。
未対応のクエリパラメータ
- エラー:
400 unsupportedFeature:Unsupported query parameter: <key> - 原因: クエリパラメータがエンドポイントの許可リストにありません。たとえば
fields、q、eventTypes、sendNotifications、supportsAttachments、maxAttendees、textです。 - 対処: そのパラメータを外します。SDK が自動で付ける
fieldsの部分レスポンスマスクも無効にします。
未対応のフィールド
- エラー:
400 unsupportedFeature:Unsupported field: <key> - 原因: リクエストボディに未対応のフィールドがあります。たとえば次の場合です。
- イベントの
eventType、attachments、source、htmlLink。 - FreeBusy の
items[]で、id以外のキーを持つエントリ。
- イベントの
- 対処: 送信前に未対応のフィールドを取り除きます。詳しくはCalendar API の互換性を参照してください。
JSON 以外のレスポンス形式
- エラー:
400 invalidParameter:Only JSON responses are supported. - 原因: クエリに
alt=mediaかalt=protoが含まれています。 - 対処:
altを省略するか、alt=jsonを使います。
タイムスタンプにオフセットがない
- エラー:
400:Expected an RFC3339 timestamp with an offset.またはdateTime requires an offset or timeZone. - 原因: 次のいずれかです。
timeMin、timeMax、updatedMin、または FreeBusy の範囲にZも±HH:MMもない。- イベントの
dateTimeにオフセットもtimeZoneもない。
- 対処:
Zか±HH:MMのオフセットを付けます。またはイベントのstart/endにtimeZoneを指定します。
ローカル時刻が不正またはあいまい
- エラー:
400:Invalid or ambiguous local time; supply an explicit offset. - 原因: オフセットのない
dateTimeが、timeZoneの夏時間の切り替えで存在しない時刻、または重複する時刻に当たっています。 - 対処:
dateTimeに数値のオフセットを明示します。
繰り返しイベントのタイムゾーンや UNTIL のエラー
- エラー:
400:Recurring timed events require start.timeZone.またはTimed recurrence UNTIL must be UTC. - 原因: 時刻指定の繰り返しイベントに
start.timeZoneがないか、UNTILの値がZで終わっていません。 - 対処:
start.timeZoneを設定します。時刻指定のUNTILは UTC(...Z)で書きます。詳しくはイベント、繰り返し、時刻の扱いを参照してください。
未対応の繰り返しルールや展開
- エラー: 繰り返しルールや展開で
400 unsupportedFeature - 原因: 次のいずれかです。
- ルールで
HOURLYかMINUTELYを使った。 - ルールに未対応の要素(
BYHOUR、BYWEEKNO、BYYEARDAY)を含めた。 - ルールにほかの iCalendar の行(
DTSTART)を含めた。 - 夏時間の切り替えで存在しない時刻に当たる回がある。
- 終わりのないルールを
timeMaxなしで展開した。 - 展開が 10,000 インスタンスか 100 年を超えた。
- ルールで
- 対処: 対応している
DAILY、WEEKLY、MONTHLY、YEARLYのルールを使います。timeMaxを指定し、範囲も狭くします。詳しくはイベント、繰り返し、時刻の扱いを参照してください。
singleEvents なしの orderBy=startTime
- エラー:
400:startTime ordering requires singleEvents=true. - 原因:
singleEvents=trueなしでorderBy=startTimeを送りました。 - 対処:
events.listのクエリにsingleEvents=trueを追加します。
繰り返しの系列ではないイベント
- エラー:
400:Event is not a recurring series. - 原因: 繰り返しではないイベントに
events.instancesを呼びました。 - 対処:
events.instancesは繰り返しの系列の親イベントにだけ使います。
読み取り専用のフィールドを変更した
- エラー:
400:<key> is read-only. - 原因: 次のいずれかです。
- Calendar API の書き込みで
organizer、creator、iCalUID、recurringEventId、originalStartTimeを変更した。 calendarListの書き込みでsummary、timeZone、accessRole、primary、kind、deleted、conferencePropertiesを変更した。
- Calendar API の書き込みで
- 対処: そのフィールドを省略するか、既存の値を変えずに送ります。
conferenceDataVersion なしの conferenceData
- エラー:
400:conferenceData requires conferenceDataVersion=1. - 原因: イベントのペイロードに
conferenceDataがありますが、?conferenceDataVersion=1がありません。 - 対処: クエリ文字列に
conferenceDataVersion=1を追加します。
プライマリカレンダーはリストから外せない
- エラー:
403 cannotDeletePrimaryCalendar:Cannot remove the primary calendar from the calendar list. - 原因: ユーザーのプライマリカレンダーに
calendarList.deleteを呼びました。 - 対処: プライマリカレンダーはリストに残します。表示しない場合は
calendarList.patch(hidden: trueかselected: false)を使います。
招待されたユーザーの編集が拒否される
- エラー:
403 forbiddenForNonOrganizer - 原因: 招待されたユーザーが招待のコピーを編集する際に、次のいずれかを行いました。
PUTを使った。- 共有されているイベントのフィールドを変更した。
- 別の参加者を編集した。
- 対処:
PATCHを使います。自分のresponseStatus/comment、reminders、colorId、transparency、extendedPropertiesだけを更新します。
イベントやカレンダーリストのエントリの重複
- エラー:
409 duplicateと、次のいずれかのメッセージDuplicate event ID.Invitation event ID conflicts with an existing local event.Calendar already exists in the calendar list.
- 原因: 次のいずれかです。
events.insertがそのカレンダーの既存の ID を使った。- 参加者への招待のコピーが、参加者のプライマリカレンダーにある同じ ID で
iCalUIDが異なるイベントと衝突した。たとえば、Fixture やテスト制御 API でiCalUIDを設定したイベントです。同じ ID でデフォルトのiCalUIDを持つイベントは、招待のコピーで上書きされます。 - すでにリストにあるカレンダー(
primaryを含む)にcalendarList.insertを呼んだ。
- 対処: 一意のイベント ID を使います。
calendarList.insertは、リストにないカレンダーにだけ呼びます。
削除済みのイベント
- エラー:
410 deleted:Event is already deleted. - 原因: すでに
cancelledになっているイベントにevents.deleteを呼びました。 - 対処: 削除時の
410は、削除済みとして扱います。
etag の不一致
- エラー:
412 conditionNotMet:Event has changed.またはCalendar list entry has changed. - 原因:
If-Matchヘッダーが、イベントまたはカレンダーリストのエントリの現在のetagと一致しません。 - 対処:
events.getかcalendarList.getで取得し直してから、再試行します。
リクエストボディが大きすぎる
- エラー:
413 payloadTooLarge:Request body too large. - 原因: リクエストボディが 1,048,576 バイト(1 MiB)を超えています。
- 対処: ペイロードを小さくします。
maxResults が範囲外
- エラー:
400:maxResults must be between 1 and 2500. - 原因:
maxResultsが0、負の値、または2500より大きい値です。 - 対処:
maxResultsを1から2500の範囲にします。
Python SDK が Google から discovery ドキュメントを取得する
- 原因:
googleapiclient.discovery.buildが Google から discovery のメタデータをダウンロードしようとしました。 - 対処:
google-api-python-client2.x を使い、static_discovery=Trueとcache_discovery=Falseを指定します。リモートの discovery URL は設定しません。詳しくはSDK の使用例を参照してください。
差分同期とページネーション
同期トークンが不正または期限切れ
- エラー:
410 fullSyncRequired:Sync token is invalid or expired. Perform a full synchronization. - 原因: 次のいずれかです。
syncTokenが 7 日より古い。- ワークスペース内のトークンが 100 個を超え、
syncTokenが破棄された。 syncTokenが別のカレンダーやユーザーのものである。- リセット、Fixture の読み込み、インメモリでの再起動で
syncTokenが消えた。
- 対処: ローカルの同期状態を破棄し、
syncTokenなしのevents.listでフル同期をやり直します。詳しくは差分同期を参照してください。
同期のクエリオプションが変わった
- エラー:
400:Sync query options must match the initial synchronization. - 原因:
singleEvents、または実際に使われるtimeZoneが最初の同期と違います。timeZoneを省略していて、カレンダーのタイムゾーンが変わった場合も含みます。 - 対処: 最初のフル同期と同じ
singleEventsとtimeZoneを指定します。
syncToken と組み合わせられないパラメータ
- エラー:
400と、次のいずれかのメッセージ<key> cannot be combined with syncToken.Incremental synchronization must include deleted events.instances does not support syncToken.
- 原因: 次のいずれかです。
syncTokenをtimeMin、timeMax、orderBy、updatedMin、iCalUID、拡張プロパティのフィルタと一緒に送った。syncTokenをshowDeleted=falseと一緒に送った。syncTokenをevents.instancesで使った。
- 対処:
events.listにはsyncTokenだけを送ります。一緒に送れるのは、最初と同じsingleEventsとtimeZone、maxResults、pageToken、showDeleted=trueです。
ページトークンが不正または期限切れ
- エラー:
410 invalidPageToken:Invalid or expired page token.またはInvalid or stale page token. - 原因: ページトークンが無効になっています。
- イベントの
pageTokenが期限切れになった(有効期間 1 時間)、破棄された(固定されたページが 1,000 を超えた)、またはクエリのオプションの変更やサーバーの再起動・リセットで無効になった。 - ワークスペースのデータに書き込みがあった後に、
calendarListのpageTokenを使った。
- イベントの
- 対処: 最初のページから一覧を取得し直します。
watch 通知
watch のホストが許可されていない
- エラー:
400:Watch host is not allowed. Configure CALENDARPIT_WATCH_HOSTS explicitly. - 原因: コールバックの
addressのホスト名が、127.0.0.1、[::1]、localhostのいずれでもありません。たとえばhost.docker.internalやコンテナのサービス名です。 - 対処:
CALENDARPIT_WATCH_HOSTS=<host1>,<host2>を設定して Calendarpit を起動します。ホスト名だけを指定し、ポートは含めません。詳しくはwatch 通知を参照してください。
watch のコールバック URL が不正
- エラー:
400:Invalid watch callback URL. - 原因:
addressがhttp:でもhttps:でもないか、認証情報や#fragmentを含んでいます。 - 対処: 認証情報やフラグメントを含まない
http://かhttps://のコールバック URL を使います。
チャネルのフィールドが不正
- エラー:
400と、次のいずれかのメッセージOnly web_hook channels are supported.Invalid channel ID.Invalid channel token.Invalid expiration.
- 原因: 次のいずれかです。
typeが"web_hook"ではない。idが[A-Za-z0-9_-]{1,64}に一致しない。tokenが 256 文字を超えるか、改行を含む。expirationが未来の Unix ミリ秒の整数ではない。
- 対処:
events.watchかcalendarList.watchのリクエストボディで、不正なフィールドを直します。
チャネル ID の重複
- エラー:
409 duplicate:Duplicate channel ID. - 原因: ワークスペース内の別の有効な watch チャネルが、すでにその
idを使っています。 - 対処: チャネルごとに新しい一意の
id(UUID など)を生成します。
watch は成功するが、アプリに通知が届かない
- 原因: Webhook の受信側への配信に失敗しています。3 回試しても失敗すると、その配信は
failedになります。受信側が次のいずれかに当たると、その試行は失敗します。- 接続できない。
- 2,000 ms 以内に応答しない。
200、201、202、204以外のステータスを返す。3xx のリダイレクトはたどりません。
- 対処: 管理画面の Watch(
http://127.0.0.1:4173/watch)で、配信ログの状態と HTTP ステータスコードを確認します。テストではPOST /__calendarpit/deliveries/drainを呼び、待機中の配信が終わるまで待ちます。
再起動、リセット、取り消しの後に通知が届かなくなる
- 原因: 次のいずれかです。
- 再起動すると、キューにある配信は取り消されます。
- インメモリでの再起動では、チャネルも消えます。
- コールバックのホストが許可されなくなった状態で再起動すると、チャネルが消えます。
- リセット、Fixture の読み込み、接続の取り消しでは、チャネルが停止します。
- カレンダーを削除すると、そのカレンダーのイベントのチャネルが停止します。
- チャネルの期限が切れました。期限はデフォルトで 24 時間、最大 7 日です。
- 対処: 再起動の後は状態を取得し直します。チャネルが停止したか期限切れになっている場合は、新しい watch チャネルを登録します。
channels.stop でチャネルが見つからない
- エラー:
channels.stopで404 notFound:Channel not found. - 原因: 次のいずれかです。
resourceIdが違う。- チャネル ID が存在しない。
- チャネルがすでに停止したか、期限切れ後に削除されている。
- チャネルを作成した接続とは別の接続のトークンで、
channels.stopを呼んだ。
- 対処: チャネルを作成した接続のトークンと、一致する
resourceIdでchannels.stopを呼びます。期限切れでも、まだ削除されていないチャネルには204が返ります。watch 通知を参照してください。
管理 API
管理 API の書き込みには JSON が必要
- エラー:
415 unsupportedMediaType:Management writes require application/json. - 原因:
/__calendarpit/*へのPOST、PATCH、PUT、DELETEリクエストをContent-Type: application/jsonなしで送りました。/resetや/deliveries/drainのようにボディのない呼び出しも対象です。 - 対処: 管理 API へのすべての書き込みリクエストに
-H 'Content-Type: application/json'を付けます。詳しくはテスト制御 APIを参照してください。
別オリジンからの管理リクエスト
- エラー:
403 forbidden:Cross-origin management requests are not allowed. - 原因: 別オリジンのブラウザのスクリプトが
/__calendarpit/*を呼びました。 - 対処:
/__calendarpit/*は、テストランナー、CLI、バックエンドから、別オリジンのOriginヘッダーを付けずに呼びます。または管理画面を使います。
state のレスポンスを Fixture として送った
- エラー:
POST /__calendarpit/fixturesで400 unsupportedFeature:Unsupported field: storage - 原因:
GET /__calendarpit/stateのレスポンスを、そのまま Fixture として送りました。 - 対処:
users、calendars、events、clientsだけを持つ正しい Fixture のオブジェクトを送ります。clientsにはクライアントのsecretの値を含めます。詳しくはFixtureを参照してください。