. → frontend に変更するのは PATCH /v9/projects/shift-scheduler-ai の1回だけshift-scheduler-ai-sepia.vercel.app は dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq(otzjjwe68)に紐づいたままprj_1atBNQYyVnXbrisEdejO4XA7J3nAshift-scheduler-aiteam_bjNOf7D5Ocbim0GFcZjFvjph(MNML-LLC).(リポジトリのルート)問題箇所npm run builddisthttps://shift-scheduler-ai-sepia.vercel.appdpl_DBWQTztKF4kpqA7Abz8kpjr31Beqotzjjwe68(shift-scheduler-otzjjwe68-mnml-llc.vercel.app)BASIC_AUTH_ENABLED=true、BASIC_AUTH_CREDENTIALS 暗号化保存)shift/)にある package.json には build スクリプトが存在しない。frontend/ 配下にあり、そこに build: vite build が定義されている。. を見に行くため npm run build が Missing script: build で失敗する。
.github/workflows/deploy.yml)が working-directory: ./frontend を明示指定して vercel deploy --prod を実行。CLI経由の場合はカレントディレクトリの内容がアップロードされるため Root Directory設定を回避できている. のため失敗するfrontend/ から親ディレクトリ参照の有無(researcher 実証済)frontend/ 配下は自己完結。package.json / vite.config.* / tsconfig.json / middleware.js のいずれも親フォルダ(../)を参照しない。Root Directory を frontend に変更しても import 解決は破綻しない。
| 案 | 操作 | CEOブラウザ操作 | 採否 |
|---|---|---|---|
| A: Vercel REST API | PATCH /v9/projects/shift-scheduler-ai に {"rootDirectory": "frontend"} |
ゼロ W層がスクリプト実行するのみ | 採用 |
| B: Vercel Dashboard 手動 | ブラウザで Project Settings → General → Root Directory を frontend に変更 → Save |
必要 CEOが手動でクリック | 却下: CEO指示「ブラウザ操作は全部こちら側でやる」に反する |
| C: vercel CLI | — | — | 却下: CLIには Root Directory を書き換えるサブコマンドが存在しない(vercel project は list / add / rm のみ) |
PATCH /v9/projects/{idOrName} の rootDirectory フィールド更新が明記されているすべてのAPI呼び出しは以下の共通ヘッダーを使う。$VERCEL_TOKEN は W層ローカル環境変数または ~/.vercel/auth.json から取得(9章参照)。teamId は クエリで指定。
export VERCEL_TOKEN="..." export TEAM_ID="team_bjNOf7D5Ocbim0GFcZjFvjph" export PROJECT="shift-scheduler-ai" export API="https://api.vercel.com"
VERCEL_TOKEN の疎通確認(GET /v2/user)curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v2/user" | jq '{username: .user.username, email: .user.email}'
期待: {"username":"...", "email":"..."} が返る。401 の場合はトークン失効。
curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v9/projects/$PROJECT?teamId=$TEAM_ID" \
| jq '{id, name, framework, rootDirectory, buildCommand, outputDirectory, nodeVersion}' \
| tee /tmp/vercel_project_before_20260702.json
期待: rootDirectory: null(= . と同義)または "."、その他は 1.1 の記載通り。
rootDirectory が null または "." であることを確認framework が "vite" であることを確認curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v6/deployments?projectId=prj_1atBNQYyVnXbrisEdejO4XA7J3nA&teamId=$TEAM_ID&target=production&limit=5" \
| jq '.deployments[] | {uid, url, state, target, created, aliasAssigned}' \
| tee /tmp/vercel_deployments_before_20260702.json
期待: 先頭行に uid: "dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq"、state: "READY"、target: "production"。
dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq が返るREADYcurl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v4/aliases?projectId=prj_1atBNQYyVnXbrisEdejO4XA7J3nA&teamId=$TEAM_ID&limit=20" \
| jq '.aliases[] | {alias, deploymentId, created}' \
| tee /tmp/vercel_aliases_before_20260702.json
期待: alias: "shift-scheduler-ai-sepia.vercel.app" の deploymentId が dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq。
dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq に紐づいているcurl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v9/projects/$PROJECT/env?teamId=$TEAM_ID" \
| jq '.envs[] | {key, target, type, updatedAt}' \
| tee /tmp/vercel_env_before_20260702.json
期待: 12エントリ(8キー × 環境)。値は返らない(暗号化)。以下の8キーが揃っていることを確認する。
| キー名 | 期待される有効環境 |
|---|---|
VITE_BACKEND_API_URL | production / preview / development |
VITE_API_URL | production / preview / development |
BASIC_AUTH_ENABLED | production / preview |
BASIC_AUTH_CREDENTIALS | production / preview |
VITE_LIFF_ID | production / preview / development |
VITE_DEFAULT_TENANT_ID | production / preview / development |
VITE_DEFAULT_STORE_ID | production / preview / development |
VITE_PASSWORD_PROTECTION_ENABLED | production / preview / development |
curl で1回の PATCH を発行するだけで完了する。以下はコード例(実行はまだしない。M層承認後に W層が実施)。
curl -sS -X PATCH \
-H "Authorization: Bearer $VERCEL_TOKEN" \
-H "Content-Type: application/json" \
"$API/v9/projects/$PROJECT?teamId=$TEAM_ID" \
-d '{"rootDirectory": "frontend"}' \
| jq '{id, name, rootDirectory, updatedAt}'
import os, httpx, json
VERCEL_TOKEN = os.environ["VERCEL_TOKEN"]
TEAM_ID = "team_bjNOf7D5Ocbim0GFcZjFvjph"
PROJECT = "shift-scheduler-ai"
with httpx.Client(base_url="https://api.vercel.com", timeout=30.0) as client:
resp = client.patch(
f"/v9/projects/{PROJECT}",
params={"teamId": TEAM_ID},
headers={"Authorization": f"Bearer {VERCEL_TOKEN}"},
json={"rootDirectory": "frontend"},
)
resp.raise_for_status()
data = resp.json()
print(json.dumps({
"id": data["id"],
"name": data["name"],
"rootDirectory": data["rootDirectory"],
"updatedAt": data.get("updatedAt"),
}, indent=2))
curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v9/projects/$PROJECT?teamId=$TEAM_ID" \
| jq '{rootDirectory, framework, buildCommand, outputDirectory}' \
| tee /tmp/vercel_project_after_20260702.json
rootDirectory が "frontend" になっているcurl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v9/projects/$PROJECT/env?teamId=$TEAM_ID" \
| jq '.envs[] | {key, target, type}' \
| tee /tmp/vercel_env_after_20260702.json
diff /tmp/vercel_env_before_20260702.json /tmp/vercel_env_after_20260702.json
期待: diff がゼロ行(環境変数はRoot Directory変更の影響を受けない)。
curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v6/deployments?projectId=prj_1atBNQYyVnXbrisEdejO4XA7J3nA&teamId=$TEAM_ID&limit=5" \
| jq '.deployments[] | {uid, state, target, created}'
期待: 最上段は依然として dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq。新規デプロイは作成されていない。
shift-scheduler-ai-sepia.vercel.app は、特定のデプロイID otzjjwe68(dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq、39日前、Ready状態)に個別に紐づいている。この紐づけは「デプロイが成功して本番昇格」の瞬間に確定するもので、プロジェクト設定を変更しても、エイリアスは明示的な再エイリアス操作なしには動かない。otzjjwe68 のビルド成果物(HTML / JS / CSS)は Vercel の CDN 上に不変(immutable)で保持されており、Root Directory設定を変えてもこの成果物には触れられない。したがって sepia の応答内容は1バイトも変わらない。
4.1 の PATCH 直後、以下2つのAPIで sepia の指し先が変わっていないことを機械的に確認できる。
# (a) alias → deploymentId の紐づけ確認
curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v4/aliases/shift-scheduler-ai-sepia.vercel.app?teamId=$TEAM_ID" \
| jq '{alias, deploymentId, projectId}'
# 期待: deploymentId が "dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq" のまま
# (b) 本番デプロイ一覧で先頭が otzjjwe68 のまま
curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v6/deployments?projectId=prj_1atBNQYyVnXbrisEdejO4XA7J3nA&teamId=$TEAM_ID&target=production&limit=1" \
| jq '.deployments[0] | {uid, state}'
# 期待: {"uid": "dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq", "state": "READY"}
| シナリオ | 発生条件 | 本番エイリアスへの影響 |
|---|---|---|
| PATCH 直後 | — | 影響なし 再デプロイは走らない |
| PATCH 後、main への push 発生時 | 誰かが main に push、または PR を main にマージ | エイリアス移動が発生 GHA と Vercel Git連携が並行して新デプロイ生成、成功した方に sepia が切り替わる(内容は同じコミット由来) |
| 手動 Redeploy 操作 | API/Dashboard で既存デプロイの再ビルドを明示操作 | エイリアス移動 実施しない |
# 401 が返り、WWW-Authenticate: Basic が付与されていることを確認
curl -sS -o /dev/null -w "HTTP %{http_code}\n" \
"https://shift-scheduler-ai-sepia.vercel.app/"
curl -sSI "https://shift-scheduler-ai-sepia.vercel.app/" \
| grep -i "www-authenticate"
401 が返る(Basic Auth 保護が継続)WWW-Authenticate: Basic realm=... ヘッダーが返る200 が返る(値は Vercel 上で暗号化保存されているため、CEOがブラウザで一度確認する方が安全)curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \ "$API/v4/aliases/shift-scheduler-ai-sepia.vercel.app?teamId=$TEAM_ID" \ | jq '.deploymentId' # 期待: "dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq"
cd ~/Dev/mnml/shift git checkout -b test/vercel-root-dir-verify echo "" >> README.md git add README.md git commit -m "test: verify vercel preview after root dir fix" git push origin test/vercel-root-dir-verify
gh pr create --draft \ --title "test: verify vercel preview after root dir fix" \ --body "verification only. do not merge."
curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v6/deployments?projectId=prj_1atBNQYyVnXbrisEdejO4XA7J3nA&teamId=$TEAM_ID&target=preview&limit=5" \
| jq '.deployments[] | {uid, state, target, meta: .meta.githubCommitRef, created}'
期待: 最新のfeature branch(test/vercel-root-dir-verify)を meta.githubCommitRef に持つプレビューデプロイが state: "READY" になっている(Vercel Git連携経由)。
DEPL_ID=$(curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \ "$API/v6/deployments?projectId=prj_1atBNQYyVnXbrisEdejO4XA7J3nA&teamId=$TEAM_ID&target=preview&limit=1" \ | jq -r '.deployments[0].uid') curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \ "$API/v3/deployments/$DEPL_ID/events?teamId=$TEAM_ID&limit=200" \ | jq -r '.[] | .text' | grep -Ei "build|error|missing" | head -30
PREV_URL=$(curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v6/deployments?projectId=prj_1atBNQYyVnXbrisEdejO4XA7J3nA&teamId=$TEAM_ID&target=preview&limit=1" \
| jq -r '.deployments[0].url')
curl -sS -o /dev/null -w "HTTP %{http_code}\n" "https://$PREV_URL/"
gh pr close --delete-branch $(gh pr view --json number -q .number)
frontend → null)curl -sS -X PATCH \
-H "Authorization: Bearer $VERCEL_TOKEN" \
-H "Content-Type: application/json" \
"$API/v9/projects/$PROJECT?teamId=$TEAM_ID" \
-d '{"rootDirectory": null}' \
| jq '{rootDirectory, updatedAt}'
# 期待: {"rootDirectory": null, ...}
curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
"$API/v4/aliases/shift-scheduler-ai-sepia.vercel.app?teamId=$TEAM_ID" \
| jq '{alias, deploymentId}'
dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq に再エイリアス(POST /v2/aliases):
curl -sS -X POST \
-H "Authorization: Bearer $VERCEL_TOKEN" \
-H "Content-Type: application/json" \
"$API/v2/aliases?teamId=$TEAM_ID" \
-d '{
"alias": "shift-scheduler-ai-sepia.vercel.app",
"deploymentId": "dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq"
}' | jq '{alias, deploymentId, status}'
参考: 公式では POST /v13/deployments/{id}/aliases も使えるが、deploymentId を直接指定できる /v2/aliases を採用(コマンド1回で済む)。
GET /v4/aliases/shift-scheduler-ai-sepia.vercel.app で指し先が dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq に戻ったことを確認HTTP 401 と WWW-Authenticate: Basic が返ることを確認)Root Directory 変更 PATCH 直後は再デプロイは走らないが、その後の main への push は Vercel Git連携経由でも成功するようになる。GHA workflow(deploy.yml)も同じ push を契機に vercel deploy --prod を実行するため、両方が並列に本番デプロイを作成し、成功した方に sepia エイリアスが切り替わるrace condition が発生する。
検証が完了するまでの間、main への push を止めることでこの race を回避する。
#dev チャンネルに以下を投稿(M層 shift 経由):
【MAIN凍結】shift-scheduler-ai リポジトリの main への push を一時凍結します - 対象リポジトリ: MNML-LLC/shift-scheduler-ai の main ブランチ - 期間: 本投稿から検証完了通知まで(見積30分〜1時間) - 理由: Vercel Root Directory 変更の検証中 - feature branch への push / PR 作成は OK - main へのマージのみ待機 - 完了は同スレッドに通知
# 現状の保護ルール確認(読み取り) gh api repos/MNML-LLC/shift-scheduler-ai/branches/main/protection # 凍結中のみ enforce_admins を true にする(M層承認後の書き込み) # gh api -X PATCH repos/MNML-LLC/shift-scheduler-ai/branches/main/protection/enforce_admins※ 本手順書では読み取りのみ。実際の書き込みは M層承認が別途必要。
【MAIN凍結解除】検証完了。main への push/マージを再開できます - 実施内容: Root Directory を frontend に変更 - 本番稼働確認: sepia が dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq に紐づいたまま継続稼働 - プレビュー成功確認: 済み
凍結中に他者が誤って main に push した場合:
GET /v6/deployments?target=production&limit=3 で新デプロイの状態を確認READY かつ sepia が新デプロイに切り替わっている場合、7.2 の手順で dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq に再エイリアスして復元(M層承認必須)researcher(読み取り系)+ coder(PATCH/POST 系)<<DELEGATE>> タグで委譲agents/workers/architect/output/ に残すVERCEL_TOKEN の取得元(優先順位)echo $VERCEL_TOKEN で取得可能なら最優先~/.vercel/auth.json: Mac mini 上の Vercel CLI ログイン済トークン。以下で取得:
jq -r '.token' ~/.vercel/auth.json※ このファイルは vercel CLI が管理。CEO が過去に一度
vercel login 済み。
gh secret list --repo MNML-LLC/shift-scheduler-ai で VERCEL_TOKEN の存在確認は可能だが、値は取得不可(GitHub仕様)。W層ローカルからの API 呼び出しには使えないため、1 または 2 を使う<<DELEGATE>>: 3章(実施前チェックリスト)の全スナップショット取得を実行<<DELEGATE>>: 4章(PATCH 実行)を実行<<DELEGATE>>: 6章(検証手順)を実行VERCEL_TOKEN を出力しない(curl の -v は使わない)BASIC_AUTH_CREDENTIALS 等)は API では返らない(暗号化保存されているため)。ログに含める心配はない/tmp/vercel_*_20260702.json)は検証完了後に削除:
rm /tmp/vercel_project_before_20260702.json \ /tmp/vercel_project_after_20260702.json \ /tmp/vercel_deployments_before_20260702.json \ /tmp/vercel_aliases_before_20260702.json \ /tmp/vercel_env_before_20260702.json \ /tmp/vercel_env_after_20260702.json
prj_41LW5LMmMkWOXcRZJrmrE5XYRLQf.(現状)frontend/ サブフォルダなし。リポジトリのルートが直接デプロイ対象buildCommand: null、outputDirectory: "."(静的サイトホスティング設定)lint-and-format.yml のみ)buildCommand: null)。Root Directory = . のままで正常動作しており、プレビュー失敗も発生していない。
vercel.json(リポジトリ側)の変更deploy.yml の変更VERCEL_TOKEN がある(環境変数 or ~/.vercel/auth.json)api.vercel.com へのHTTPS疎通が可能#dev チャンネルへの投稿権限がある(凍結周知用)| 案 | 内容 | 採否 | 理由 |
|---|---|---|---|
| 案A | Vercel REST API で PATCH | 採用 | CEOブラウザ操作ゼロを実現。Vercel公式仕様で裏付け済 |
| 案B | Vercel Dashboard手動 | 却下 | CEO指示「ブラウザ操作は全部こちら側でやる」に反する |
| 案C | vercel CLI経由 | 却下 | CLIにRoot Directory書き換えコマンドが存在しない |
| 案D | リポジトリ側 vercel.json にRoot Directoryを記載 | 却下 | Vercelの vercel.json はRoot Directoryのオーバーライドをサポートしない(Dashboard/API設定が最終権威) |
rootDirectory フィールド仕様が将来変わる可能性は低いが、実施直前にresearcherが最新の公式ドキュメント(https://vercel.com/docs/rest-api/reference/endpoints/projects)を再確認すること~/.vercel/auth.json のトークンが失効している場合、CEOに vercel login を依頼する必要が発生する(ブラウザ操作が不可避)。3.1 の疎通確認で早期検出する