Vercel Root Directory 修正 実施手順書(API完全自動化版)

対象: shift-scheduler-ai(Vercelプロジェクト)/ 作成: architect W層(2026-07-02 改訂)/ Issue: shift M層 DELEGATE / 改訂点: Dashboard手動 → REST API完全自動化に差し替え
この手順書の要点(30秒サマリ)
目次
  1. 現状の把握
  2. 実施方式の選定(REST API を採用)
  3. 実施前チェックリスト(API版)
  4. 実施ステップ(API版)
  5. 本番影響ゼロの根拠
  6. 検証手順(API版)
  7. ロールバック手順(API版)
  8. main push 凍結期間の運用
  9. 実装先の指定(W層オンザフライ実行)
  10. shift-scheduler-ai-liff の判定
  11. 未確認事項・CEO確認事項

1. 現状の把握

1.1 Vercelプロジェクト設定(読み取り実施済)

プロジェクトID
prj_1atBNQYyVnXbrisEdejO4XA7J3nA
プロジェクト名(API idOrName)
shift-scheduler-ai
組織ID(teamId)
team_bjNOf7D5Ocbim0GFcZjFvjph(MNML-LLC)
Root Directory(現在)
.(リポジトリのルート)問題箇所
Framework Preset
Vite
Build Command
npm run build
Output Directory
dist
Node.js Version
24.x

1.2 現行本番の状態(ロールバック用に記録)

本番URL(alias)
https://shift-scheduler-ai-sepia.vercel.app
紐づくデプロイID
dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq
短縮ID
otzjjwe68shift-scheduler-otzjjwe68-mnml-llc.vercel.app
作成日時
2026-05-24 03:08:24 JST(39日前)
Status
● Ready(正常稼働中)
Basic Auth
有効(BASIC_AUTH_ENABLED=trueBASIC_AUTH_CREDENTIALS 暗号化保存)

1.3 プレビューデプロイが失敗している原因

リポジトリのルート(shift/)にある package.json には build スクリプトが存在しない。
一方、実際のフロントエンドは frontend/ 配下にあり、そこに build: vite build が定義されている。
Vercelは Root Directory = . を見に行くため npm run buildMissing script: build で失敗する。

1.4 現状の"本番だけ動いている"仕組み

1.5 frontend/ から親ディレクトリ参照の有無(researcher 実証済)

frontend/ 配下は自己完結。package.json / vite.config.* / tsconfig.json / middleware.js のいずれも親フォルダ(../)を参照しない。Root Directory を frontend に変更しても import 解決は破綻しない。

2. 実施方式の選定(REST API を採用)

2.1 検討した3案

操作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 のみ)

2.2 REST API が本タスクに適する理由

3. 実施前チェックリスト(API版)

すべての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"

3.1 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 の場合はトークン失効。

3.2 現行プロジェクト設定のスナップショット(GET /v9/projects/{project})

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 の記載通り。

3.3 現行本番デプロイのスナップショット(GET /v6/deployments)

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"

3.4 現行本番エイリアスのスナップショット(GET /v4/aliases)

curl -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"deploymentIddpl_DBWQTztKF4kpqA7Abz8kpjr31Beq

3.5 環境変数一覧のスナップショット(GET /v9/projects/{project}/env)

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_URLproduction / preview / development
VITE_API_URLproduction / preview / development
BASIC_AUTH_ENABLEDproduction / preview
BASIC_AUTH_CREDENTIALSproduction / preview
VITE_LIFF_IDproduction / preview / development
VITE_DEFAULT_TENANT_IDproduction / preview / development
VITE_DEFAULT_STORE_IDproduction / preview / development
VITE_PASSWORD_PROTECTION_ENABLEDproduction / preview / development

4. 実施ステップ(API版)

4.1 Root Directory 変更(PATCH /v9/projects/{project})

curl で1回の PATCH を発行するだけで完了する。以下はコード例(実行はまだしない。M層承認後に W層が実施)。

curl 版

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}'

Python 版(W層 researcher / coder がオンザフライ実行)

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))

4.2 変更確認(GET /v9/projects/{project})

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

4.3 環境変数の再確認(GET /v9/projects/{project}/env)

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変更の影響を受けない)。

4.4 自動再デプロイは走らないことの確認(GET /v6/deployments)

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。新規デプロイは作成されていない。

Vercel公式仕様: Project Settings の変更は次のデプロイ時に反映される。設定PATCHは既存デプロイに影響しない。

5. 本番影響ゼロの根拠

5.1 現行本番エイリアスが動かない理由

Vercelの本番エイリアス shift-scheduler-ai-sepia.vercel.app は、特定のデプロイID otzjjwe68dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq、39日前、Ready状態)に個別に紐づいている。この紐づけは「デプロイが成功して本番昇格」の瞬間に確定するもので、プロジェクト設定を変更しても、エイリアスは明示的な再エイリアス操作なしには動かない

既存デプロイ otzjjwe68 のビルド成果物(HTML / JS / CSS)は Vercel の CDN 上に不変(immutable)で保持されており、Root Directory設定を変えてもこの成果物には触れられない。したがって sepia の応答内容は1バイトも変わらない

5.2 API で継続稼働を確認する手順

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"}

5.3 変更中に本番エイリアスが別デプロイに切り替わるリスク

シナリオ発生条件本番エイリアスへの影響
PATCH 直後 影響なし 再デプロイは走らない
PATCH 後、main への push 発生時 誰かが main に push、または PR を main にマージ エイリアス移動が発生 GHA と Vercel Git連携が並行して新デプロイ生成、成功した方に sepia が切り替わる(内容は同じコミット由来)
手動 Redeploy 操作 API/Dashboard で既存デプロイの再ビルドを明示操作 エイリアス移動 実施しない

6. 検証手順(API版)

6.1 本番URL疎通確認(curl で Basic Auth 継続動作)

# 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"

6.2 sepia が otzjjwe68 に紐づいたままであることを再確認

curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
  "$API/v4/aliases/shift-scheduler-ai-sepia.vercel.app?teamId=$TEAM_ID" \
  | jq '.deploymentId'

# 期待: "dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq"

6.3 ダミーPRでプレビュー成功確認(API版)

  1. ローカルで軽微な変更(例: README末尾に空行1個追加)を行い、feature branch を作成:
    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
  2. GitHubで PR を作成(ドラフトPRでよい)。以下で作成可能:
    gh pr create --draft \
      --title "test: verify vercel preview after root dir fix" \
      --body "verification only. do not merge."
  3. 数分後、プレビューデプロイの状態を API で確認:
    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連携経由)。
  4. ビルドログを確認して "Missing script: build" が消えていることを確認:
    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
  5. プレビューURLを curl で疎通確認(本番と同じく 401 Basic Auth が返るはず):
    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/"
  6. 確認完了後、ドラフトPRをクローズし、ブランチを削除:
    gh pr close --delete-branch $(gh pr view --json number -q .number)

7. ロールバック手順(API版)

7.1 Root Directory を元に戻す(frontendnull

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, ...}

戻すだけの操作は再デプロイを起動しないため、本番URLへの影響は「なし」。

7.2 万が一エイリアスが別デプロイに切り替わった場合の即時復元

本手順の想定シナリオでは発生しないが、想定外の状況(他者がpush・別ワークフロー起動・並列マージ等)で sepia の指し先が変わってしまった場合の対応。実行は M層承認必須
  1. 現状の sepia 指し先を確認:
    curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \
      "$API/v4/aliases/shift-scheduler-ai-sepia.vercel.app?teamId=$TEAM_ID" \
      | jq '{alias, deploymentId}'
  2. 元の 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回で済む)。
  3. 再度 GET /v4/aliases/shift-scheduler-ai-sepia.vercel.app で指し先が dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq に戻ったことを確認
  4. 本番URLを curl で疎通確認(HTTP 401WWW-Authenticate: Basic が返ることを確認)

8. main push 凍結期間の運用

8.1 凍結が必要な理由

Root Directory 変更 PATCH 直後は再デプロイは走らないが、その後の main への push は Vercel Git連携経由でも成功するようになる。GHA workflow(deploy.yml)も同じ push を契機に vercel deploy --prod を実行するため、両方が並列に本番デプロイを作成し、成功した方に sepia エイリアスが切り替わるrace condition が発生する。

検証が完了するまでの間、main への push を止めることでこの race を回避する。

8.2 凍結期間の目安

実施〜検証完了まで 30分〜1時間 を想定。実施は researcher/coder(W層)が15分、検証(ダミーPR→プレビュー確認)が30分。

8.3 凍結の周知手順(W層が実行)

  1. 開始時: Slack の #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 へのマージのみ待機
    - 完了は同スレッドに通知
  2. 凍結中の防御: 万が一に備え、GitHub Branch Protection Rule で main への直接 push と PR マージを一時的にブロックすることも検討可能(researcher/coder が M層承認後に API で操作):
    # 現状の保護ルール確認(読み取り)
    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層承認が別途必要。
  3. 解除時: 検証完了後、同じ Slack スレッドに解除通知:
    【MAIN凍結解除】検証完了。main への push/マージを再開できます
    - 実施内容: Root Directory を frontend に変更
    - 本番稼働確認: sepia が dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq に紐づいたまま継続稼働
    - プレビュー成功確認: 済み

8.4 凍結違反時の対応

凍結中に他者が誤って main に push した場合:

  1. 即座に GET /v6/deployments?target=production&limit=3 で新デプロイの状態を確認
  2. 新デプロイが READY かつ sepia が新デプロイに切り替わっている場合、7.2 の手順で dpl_DBWQTztKF4kpqA7Abz8kpjr31Beq に再エイリアスして復元(M層承認必須)
  3. その後、通常の検証手順に戻る

9. 実装先の指定(W層オンザフライ実行)

9.1 実行主体

実施担当
W層 researcher(読み取り系)+ coder(PATCH/POST 系)
実行場所
Mac mini ローカル。M層 shift から <<DELEGATE>> タグで委譲
成果物のリポジトリコミット
しない。スクリプトはオンザフライ実行のみ。手順書(本HTML)はリポジトリ内の agents/workers/architect/output/ に残す
CEO の関与
M層 shift 経由で「実施OK / ロールバックOK」の承認のみ。ブラウザ操作なし

9.2 VERCEL_TOKEN の取得元(優先順位)

  1. W層ローカル環境変数: echo $VERCEL_TOKEN で取得可能なら最優先
  2. ~/.vercel/auth.json: Mac mini 上の Vercel CLI ログイン済トークン。以下で取得:
    jq -r '.token' ~/.vercel/auth.json
    ※ このファイルは vercel CLI が管理。CEO が過去に一度 vercel login 済み。
  3. GitHub Secret: gh secret list --repo MNML-LLC/shift-scheduler-aiVERCEL_TOKEN の存在確認は可能だが、値は取得不可(GitHub仕様)。W層ローカルからの API 呼び出しには使えないため、1 または 2 を使う

9.3 実行フロー

  1. M層 shift が本手順書を CEO にレビュー依頼
  2. CEO 承認後、M層 shift が researcher に <<DELEGATE>>: 3章(実施前チェックリスト)の全スナップショット取得を実行
  3. researcher の報告を受けて M層 shift が Slack で「MAIN凍結」宣言(8.3
  4. M層 shift が coder に <<DELEGATE>>: 4章(PATCH 実行)を実行
  5. coder の報告を受けて researcher に <<DELEGATE>>: 6章(検証手順)を実行
  6. 検証完了後、M層 shift が Slack で「MAIN凍結解除」宣言
  7. M層 shift が CEO に完了報告

9.4 セキュリティ

10. shift-scheduler-ai-liff の判定

10.1 調査結果

プロジェクトID
prj_41LW5LMmMkWOXcRZJrmrE5XYRLQf
Root Directory
.(現状)
Framework Preset
Other
リポジトリ構造
frontend/ サブフォルダなし。リポジトリのルートが直接デプロイ対象
vercel.json
buildCommand: nulloutputDirectory: "."(静的サイトホスティング設定)
直近のデプロイ状態
3件すべて Ready(Production)、失敗なし
GHAワークフロー
デプロイ用ワークフローなし(lint-and-format.yml のみ)

10.2 判定

shift-scheduler-ai-liff は同じ問題を抱えていない。対応不要。
理由: LIFFプロジェクトは静的HTML/JSをそのままVercelに置くだけの構成で、ビルドステップ自体が定義されていない(buildCommand: null)。Root Directory = . のままで正常動作しており、プレビュー失敗も発生していない。

11. 未確認事項・CEO確認事項

11.1 CEO判断を仰ぐべき事項

11.2 実施しないこと(明示的な非スコープ)

設計完了チェックリスト

前提条件

副作用・影響範囲

代替案(却下理由)

内容採否理由
案AVercel REST API で PATCH採用CEOブラウザ操作ゼロを実現。Vercel公式仕様で裏付け済
案BVercel Dashboard手動却下CEO指示「ブラウザ操作は全部こちら側でやる」に反する
案Cvercel CLI経由却下CLIにRoot Directory書き換えコマンドが存在しない
案Dリポジトリ側 vercel.json にRoot Directoryを記載却下Vercelの vercel.json はRoot Directoryのオーバーライドをサポートしない(Dashboard/API設定が最終権威)

リスク・懸念事項


architect W層 / 2026-07-02 改訂(API完全自動化版)/ DESIGN_vercel_root_dir_shift_scheduler.html