| レイヤー | 当初見積 | 実態 | ギャップの理由 |
|---|---|---|---|
| X Harness: 当選DMに一意token付与 | 半日〜1日 | ✅ 既に実装済み | OSS が appendRef(gate.link, 'xh:' + delivery.token) を engagement-gate.ts:152 で既に実装 |
| X Harness: token→ X user_id 検索API | 1日 | ✅ 既に実装済み | OSS の GET /api/tokens/:token/resolve がすでに公開エンドポイントとして稼働 |
LP: ?ref=xh: 抽出 | 半日 | 🟡 5-10行追加で完了 | URL パラメータ解析 + JSON 同梱だけ |
| LINE Harness: lp-hook.ts の ref処理 | 数時間 | 🟡 30-50行追加で完了 | 既存 decodeSource を拡張 + 新ヘルパ関数 |
| LINE Harness: friend_diagnosis 拡張 | 数時間 | 🟡 migration 111 で4カラム追加 | ALTER TABLE 1ファイル |
| X↔LINE webhook | 1日 | ❌ 不要 | OSS resolveToken() が atomic consume するため webhook 不要 |
| 合計 | 3-4日 | 約半日 | OSS既存実装の発見が時短の本体 |
export async function resolveToken(db: D1Database, token: string): Promise<ResolvedToken | null> {
// Atomically mark the token as consumed and return its data in a single statement.
// If consumed_at is already set (or token doesn't exist), the UPDATE matches zero rows
// and we return null — preventing double-redemption races.
const now = jstNow();
const row = await db
.prepare('UPDATE engagement_gate_deliveries SET consumed_at = ? WHERE token = ? AND consumed_at IS NULL RETURNING x_user_id, x_username, gate_id')
.bind(now, token)
.first<{ x_user_id: string; x_username: string | null; gate_id: string }>();
if (!row) return null;
// ... LINE Harness config を取得して返す
}
そして apps/worker/src/routes/engagement-gates.ts:120-125 には、auth 不要の public endpoint として公開されていました。
// Public endpoint — no auth required (token is the secret)
engagementGates.get('/api/tokens/:token/resolve', async (c) => {
const result = await resolveToken(c.env.DB, c.req.param('token'));
if (!result) return c.json({ success: false, error: 'Token invalid or already consumed' }, 404);
return c.json({ success: true, data: result });
});
UPDATE...RETURNING句で atomic に consume + return を1コールで完結 しています。race condition も発生しません。token 自体が64文字のランダム秘密のため、auth は不要という設計判断も合理的です。
私が当初書こうとしていた「verify-token」「claim-token」の2エンドポイント分割は、この1関数で全部置き換えられます。
X-LINE 真の連携フロー全体図
💡 KEY TAKEAWAYS
SaaS 連携実装で「自前で書く」前に OSS の README、SPEC、エンドポイント一覧を読む こと。既存機能の存在を見落として車輪の再発明をすると、工数が10倍違います。
4つの落とし穴 — 実装中につまずいた場所
実装中に4箇所で立ち止まりました。それぞれの原因と突破方法を共有します。落とし穴 1: Permission Read-only で 403 連発
実装が一通り終わって動作確認を始めたら、X API がすべて 403 で弾かれました。{
"title": "Forbidden",
"status": 403,
"detail": "Your client app is not configured with the appropriate oauth1 app permissions for this endpoint."
}
- X Developer Portal → 該当アプリ → Settings → User authentication settings → Edit
- App permissions を 「Read and write and Direct message」 に変更
- Keys and tokens タブ → Access Token を再生成(権限変更後は旧トークン無効)
- D1 の x_accounts テーブルに新トークンを UPDATE
npx wrangler d1 execute <YOUR_DB_NAME> --remote --command \
"UPDATE x_accounts SET access_token='<TK>', access_token_secret='<YOUR_TOKEN_SECRET>' WHERE id='...';"
教訓: SaaS 連携の「動かない」の8割は権限・スコープ・トークン。コードを掘る前に、まず Developer Portal でアプリ権限と最新トークンを確認すべきです。
落とし穴 2: PowerShell で bash の curl は動かない
検証用の curl コマンドを PowerShell に貼ったところ、3回連続でエラーになりました。
curl : ドライブが見つかりません。名前 'https' のドライブが存在しません。
PowerShell では curl が Invoke-WebRequest のエイリアスになっており、-s フラグが drive として解釈されています。bash の curl とは構文が完全に別物です。
突破: 3つの解決策があります。
# 方法A: curl.exe を明示
curl.exe -s "https://api.example.com" -H "Authorization: Bearer xxx"
# 方法B: PowerShell ネイティブ
$h = @{ "Authorization" = "Bearer xxx" }
Invoke-RestMethod -Uri "https://api.example.com" -Headers $h
# 方法C: git-bash で実行
# bash の構文がそのまま動く
最終的にチーム内で混在させないため、check.ps1 という PowerShell スクリプト1本 を置いて、検証時はそれを実行する規約にしました。Invoke-RestMethod ベースで書かれているため、PowerShell から .\check.ps1 で1行実行できます。
教訓: シェル混在環境ではコマンドを書くより、実行ヘルパーを置く方が生産性が高い。
落とし穴 3: がクロスプラットフォームで破壊される
LINE Harness をデプロイしようとしたら、ローカルの wrangler が起動しませんでした。
Error: Cannot find module '.../node_modules/wrangler/bin/wrangler.js'
MODULE_NOT_FOUND
原因は Mac で pnpm install した node_modules を 経由で Windows に同期したことでした。node_modules/.bin/wrangler のシンボリックリンクは存在しているのに、本体の node_modules/wrangler/ ディレクトリが部分的に欠損していました。
pnpm/npm の node_modules には OS 固有のバイナリ(workerd、、native bindings)が含まれており、クロスプラットフォーム同期で壊れます。
突破: 3つの解決策があります。
# 方法A: Nextcloud で node_modules を同期除外
# .nextcloudignore に node_modules/, .next/, dist/, .wrangler/ を追加
# 方法B: Win 側で pnpm install --force を再実行
cd line-harness-oss && pnpm install --force
# 方法C(採用): npx -y wrangler@4 で都度ダウンロード
npx -y wrangler@4 d1 execute line-harness --remote --file=migration.sql
最終的に方法C を採用しました。バージョン固定で安定し、node_modules の破壊問題から永久に解放されます。
教訓: クロスプラットフォーム同期するプロジェクトでは、node_modules を同期しない。npx の都度ダウンロードが最も安定。
落とし穴 4: bash の curl で日本語 JSON 送信が 文字化け
ゲートの replyKeyword を「鑑定希望」から「運命」に更新するために、curl で PUT リクエストを送ったら、こうなりました。
"replyKeyword": "\udcefソス^\udcefソス\udcefソス"
bash のコマンドライン引数で渡した日本語が、Shift-JIS と UTF-8 の混在環境で破壊されました。
突破: JSON を で生成してファイルに書き出し、curl の --data-binary @file で送信する。
python <<'EOF' > /tmp/gate_update.json
import json, sys
sys.stdout.buffer.write(json.dumps({"replyKeyword": "運命"}, ensure_ascii=False).encode("utf-8"))
EOF
curl -s -X PUT "$WORKER/api/engagement-gates/$GATE" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary @/tmp/gate_update.json
教訓: 日本語を含む JSON は Python 経由で生成、ファイルから送信が安全。シェル引数の文字化けは環境差で再現性が低く、デバッグ時間を奪います。

学び — OSS活用の組織体力
この実装から得た学びは3つです。
1. 自前実装の前に OSS の README / SPEC / エンドポイント一覧を読む。
コードリーディングは「面倒な前置き」ではなく、最も投資効率の高い時間です。当方は今回30分の OSS リーディングで4日を半日に圧縮しました。
2. _custom/ パターンで OSS のコアコードに触れない規約を持つ。
upstream 追従コストを最小化できます。今回も最初に書いた _custom/deliveries.ts を OSS 機能と重複と判明した時点で削除し、コア未編集ルールを完全準拠で運用継続しました。
3. SaaS 連携の「動かない」の8割は権限・スコープ・トークン。
コードを疑う前に Developer Portal のアプリ権限と最新トークンを確認する。OAuth Permission の罠は誰もが踏みます。
:::details 当方の OSS 運用ルール(参考)
# X / LINE Harness OSS 運用ルール
## コア未編集の原則
- packages/db/, apps/worker/src/routes/ は触らない
- 独自機能は apps/worker/src/_custom/ 配下に追加
- カスタムDB拡張は _custom/db/migrations/<番号>_<名前>.sql
## upstream 追従
- 月1回 git fetch upstream && git merge upstream/main
- conflict が発生しないように _custom/ 以外を編集しない
## 例外的にコアに触れる場合
- MODIFICATIONS.md に経緯と差分を記載
- 次回 upstream マージ時の手順を明記
## デプロイ前チェック
- [ ] _custom/ 配下のみ変更されているか
- [ ] migration 番号が連番になっているか
- [ ] secrets が D1 ではなく Worker secrets に入っているか
:::
次のアクション
OSS をローカル fork で運用するか、自前で1から書くか、SaaS をフルマネージドで使うかの判断は、組織の OSS活用体力 によって変わります。当方は + D1 + 自家 fork OSS の組み合わせを軸にしていますが、これは一定のコードリーディング体力を前提にしています。
OSS を読み解いて自社の仕組みを組む力を社内に持ちたい方へ。社員が自分で業務のアプリや仕組みを作れるようになる Claude Code 研修 も行っています。

