GASアプリ群への不具合報告フォーム横展開 — 各アプリを匿名公開せず共通フォーム1本へリンクする
機微データを持つ・スコープを触れない・画面が無い、で止まる横展開を、フォームをパラメータ化して共通1本に集約する方式で全部さばく。器の選び方で人間のクリックを0回にする方法と、文字化け・公開範囲の検証3点まで。
約4.9万トークンの節約 (API料金換算で約73円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「GASアプリ群への不具合報告フォーム横展開 — 各アプリを匿名公開せず共通フォーム1本へリンクする」は、Google WorkspaceカテゴリのAI指示書(MDファイル)です。機微データを持つ・スコープを触れない・画面が無い、で止まる横展開を、フォームをパラメータ化して共通1本に集約する方式で全部さばく。器の選び方で人間のクリックを0回にする方法と、文字化け・公開範囲の検証3点まで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約4.9万トークン(API料金換算で約73円)・93%のトークンを節約できます。
- カテゴリ
- Google Workspace
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約5.2万トークン
- この巻物使用時
- 約3,400トークン
- 節約量
- 約4.9万トークン (約73円)
- 更新日
- 2026-09-05
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/gas-1/raw を読み込んで、この指示書どおりに実装して"
中身
GAS アプリ群へ不具合報告フォームを横展開する — 各アプリを匿名公開せず「共通フォーム1本」へリンクする方式
社内に Google Apps Script のアプリが何本もあり、その全部に「不具合・要望」の報告窓口を付けたい、という場面の手順書。
素直にやると アプリごとにフォーム画面を実装して、そのアプリ自身を「全員(匿名)」でウェブアプリとして公開する(以下・方式A)。1〜2本ならこれで問題ないが、5本目あたりで必ず止まる。この手順書は、そこで方式を切り替えて残り全部をさばくための型。
1. 方式Aが止まる3パターン(先に判定する)
| 止まる理由 | 具体例 | なぜ方式Aが使えないか |
|---|---|---|
| 機微データを持つアプリ | 審査結果・人事評価・顧客財務・仕入価格 | 同じスクリプトを匿名公開すると、doGet の他の画面にも匿名で到達しうる。実質的に公開範囲を広げてしまう |
oauthScopes を宣言していないアプリ | 古くから動いている自動化スクリプト | スコープはコードからの自動検出に頼っている。明示リストを書いた瞬間に自動検出が無効になり、既存機能が壊れる |
| 社員向け画面が存在しないアプリ | ビーコン受信専用、JSON を返す管理用エンドポイント、アドオン | そもそもリンクを置く場所が無い |
候補判定は「ウェブアプリ設定があるか」ではなく「社員が実際に開く HTML があるか」で行う。 ウェブアプリのアクセス設定が「自分だけ」になっているプロジェクトは、たいてい管理用エンドポイントであって社員向け画面を持たない。画面が別のフロントエンド(Next.js 等)側にあるなら、それは GAS 用ではなくそちらのフォームパッケージの担当。
2. 方式B — フォームをパラメータ化して共通1本にする
フォームを提供する側のサーバ関数を、呼び出し元の名前と報告元URLを外から受け取る形にしておく。
function serveFeedbackForm(params) {
var opts = params || {};
var template = HtmlService.createTemplateFromFile('FeedbackForm');
template.appName = String(opts.app || ''); // 通知の見出しになる
template.sourceUrl = String(opts.src || ''); // 報告元ページ
return template.evaluate()
.setTitle('不具合・要望の報告')
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
function doGet(e) {
var params = (e && e.parameter) || {};
if (String(params.form || '') === 'feedback') return serveFeedbackForm(params);
return HtmlService.createHtmlOutput('このアプリには feedback フォーム以外の画面はありません。');
}
HTML 側は受け取った値を hidden に置き、送信ペイロードへそのまま載せる。
<input type="hidden" id="appNameValue" value="<?= appName ?>">
<input type="hidden" id="sourceUrlValue" value="<?= sourceUrl ?>">
var payload = { /* ... */ appName: appName, sourceUrl: sourceUrl };
テンプレートの出力は必ず
<?= ?>(エスケープあり)にする。<?!= ?>にすると、URL パラメータがそのまま HTML に流れて XSS の入口になる。外部から値を受ける設計なので、ここは方式Aより重要度が上がる。
これで、どのアプリの報告窓口も1本の URL で足りる。
<共通フォームのURL>?form=feedback&app=<アプリ名をURLエンコード>&src=<報告元ページのURL>
各アプリ側の作業はリンクを1本置くだけになる。スクリプトも、スコープも、デプロイも、公開範囲も触らない。
3. 「器」の選び方 — ここで人間のクリック回数が決まる
共通フォームをどのプロジェクトに置くかで、人間に頼む操作が 0 回か 1 回かが決まる。
- 🔴 新規にプロジェクトを作ると、初回の権限承認(ブラウザでの OAuth 同意)が必ず1回発生する。これは自動化できない。「あと1クリックだけお願いします」を毎回生むので、避けられるなら避ける
- ✅ 既に動いていて権限承認済みのプロジェクトのうち、
doGetがフォーム専用で他の画面を持たないものを器にすると、人間の操作は0回で済む。「他の画面が無い」ことが匿名公開の安全性を担保する
器は後から差し替えられる(各アプリのリンク先 URL を書き換えるだけ)ので、この選択は可逆。まず0クリックで動かし、専用プロジェクトが要るなら後で移せばよい。
弱点は1つだけ: 記録用シートが器のプロジェクト側に集約される。通知は app 名で区別できるので運用は困らない。アプリ別にシートを分けたい時だけ方式Aを選ぶ。
4. 導入手順(各アプリ側)
対象アプリの画面(HTML、またはシートのセル)に、上記 URL へのリンクを置く。それ以外は何もしない。
- 🔴 マニフェスト(
appsscript.json)を変更しない — ウェブアプリ設定もスコープも触らない - 🔴 新しいデプロイを作らない
- 🔴 サーバ側のコード(
doGet等)を変更しない - 🔴 フォーム側の設定投入も不要 — 設定は器の側に既に入っている
<div class="feedback-link">
<a href="<共通フォームURL>?form=feedback&app=%E2%80%A6" target="_blank" rel="noopener">🐛 不具合・要望</a>
</div>
ここで踏む罠
app=の値は URL エンコード済みの文字列をそのまま埋め込む。 日本語をそのまま書くと、編集経路(特に Windows のシェル経由)で文字化けする- HTML の属性値では
&を&にエスケープする。 生の&はパラメータが切れる原因になる - ページに
<base target="_top">がある場合、リンクにtarget="_blank" rel="noopener"を明示する。 無いと同じタブで開き、作業中の画面を失う - デプロイが「最新(HEAD)」ならソースの反映は即時、バージョン固定なら再デプロイするまで画面に出ない。 デプロイ一覧で必ず確認する
5. 検証(ここまでやって初めて「完了」)
対象アプリのアクセス設定がドメイン限定なら、匿名のリクエストでは画面を取得できない。次の3点で確認する。
- 共通フォームがそのアプリ名で描画されるか
app=を付けた URL を実際に取得し、返ってきた HTML にアプリ名が文字化けせず含まれることを確認する。 🔴 シェルのcurl --data-urlencodeで日本語を渡すと、Windows では文字コードが変換されて%EF%BF%BD(U+FFFD)の羅列になる。 検証は必ず Node のfetch+encodeURIComponentなど、シェルを1層も通さない経路で行う。const url = BASE + '?form=feedback&app=' + encodeURIComponent(name); const t = await (await fetch(url)).text(); console.log(t.includes(name), t.includes('�')); // true, false を期待 - 本番のスクリプトに反映されたか 別ディレクトリに同じスクリプトIDの設定を置いて pull し、追加したリンク文字列が含まれることを確認する。あわせてマニフェストが変わっていないことも差分で確認する。
- 公開範囲が広がっていないか 対象アプリの URL に匿名でアクセスし、302 でログイン画面へ飛ぶ(=制限が維持されている)ことを確認する。これを飛ばすと、フォームを足したつもりで本体を公開してしまった事故に気付けない。
器の側は、設定が入っているかを返す ping 関数を1つ用意しておくと早い。
function feedbackPing() {
var c = getConfig_();
return { ok:true, hasUrl:Boolean(c.url), hasSecret:Boolean(c.secret), appName:c.appName };
}
hasUrl / hasSecret が true で、appName が文字化けしていないことまで見る。文字化けは通知の宛先解決を壊すので、真偽値だけ見て通すと後で詰まる。
6. まとめ(判断の順序)
- そのアプリに社員が開く HTML があるか → 無ければ対象外
- 匿名公開してまずい画面が同居していないか → していなければ方式Aでもよい
- していれば方式B(リンク1本・アプリ側は無変更)
- 器は既に権限承認済みでフォーム専用のプロジェクトを選ぶ(人間のクリック0回)
- 検証は描画・反映・公開範囲の3点。1つでも飛ばさない
よくある質問
+「GASアプリ群への不具合報告フォーム横展開 — 各アプリを匿名公開せず共通フォーム1本へリンクする」とは何ですか?
機微データを持つ・スコープを触れない・画面が無い、で止まる横展開を、フォームをパラメータ化して共通1本に集約する方式で全部さばく。器の選び方で人間のクリックを0回にする方法と、文字化け・公開範囲の検証3点まで。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約5.2万トークンかかりますが、この巻物を使えば約3,400トークンで済みます。差し引き約4.9万トークン(API料金換算で約73円)・93%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
GAS完全自動化テンプレ — Driveコマンドキュー方式
Google Apps Script の「毎回エディタで▶実行」を根絶。Drive 経由のコマンドキューで、初回1クリック以降は AI がすべての GAS 関数をリモート実行できるようになるテンプレート指示書。
人間の手入力台帳を壊さずに自動更新する — GAS Web App upsert 設計
各PC/各拠点の点検結果を、人間が手運用しているスプレッドシート台帳へ自動反映する。手入力列とコメントを絶対に壊さない突合設計、タブ/列の解決、並行POST対策、配布シークレットの落とし穴まで。
数式まみれの業務スプレッドシートを、Webアプリから壊さずに編集させる型
ArrayFormula と per-row 数式が混在する台帳を、セル単位 allowlist・dry-run 既定・適用前バックアップ・触っていないセルの数式不変検査で安全に書き換える設計手順。列ごとの数式復元と、テストが緑のまま壊れる典型例つき。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア