Promote scripts/register-github-app.sh to a proper subcommand: cast github-app register #5

Closed
opened 2026-07-12 11:21:02 +00:00 by dan-claude-bot · 2 comments
dan-claude-bot commented 2026-07-12 11:21:02 +00:00 (Migrated from github.com)

Motivation

The register script's env-var-pile interface produced three real footguns in one live provisioning run (incubator prod-migration, 2026-07-12):

  1. APP_NAME silently decouples from state. The value must equal github_apps.<repo> in environments.yaml or every later cast apply fails to resolve the source — but the script takes it as a free-form env var and can't know it's wrong. The operator passed the GitHub App's display name instead of the state value; nothing caught it.
  2. Path/cwd fragility. CAST_STATE=. + a relative PRIVATE_KEY_FILE means running from the wrong directory fails (or worse, half-works) depending on cwd. A typo'd script path (~/local/… vs ~/.local/…) added a round of confusion the CLI itself would never have.
  3. WEBHOOK_SECRET is required even when the App's webhook is inactive — the correct configuration for a tailnet-only Coolify where deliveries can never arrive and deploys are CI-triggered. Operators must invent a placeholder (openssl rand -hex 16) to satisfy the check.

Proposal

cast github-app register <repo> \
  --state <dir> \
  --org <org-or-user> \
  --app-id <id> --installation-id <id> \
  --client-id <id> --client-secret-stdin \
  --private-key <path.pem> \
  [--webhook-secret <value>]   # default: generated placeholder, noted in output
  • Validates against state: resolves the Coolify-facing name from github_apps.<repo> in environments.yaml instead of accepting a free-form name — the footgun disappears structurally.
  • --client-secret-stdin (mirroring docker login --password-stdin) keeps the secret out of shell history and process listings; the current script takes it as an env var on the command line.
  • --webhook-secret optional; when omitted, generate a random value and say so (webhook-inactive Apps are a legitimate, arguably preferable, configuration for clone-only use).
  • Same API calls as the script (upload key → create github-app record); the script can stay as a thin wrapper or be removed.

Scope note

This stays a bootstrap-time, operator-run act — it doesn't blur the apply/diff reconciliation contract (semantics.md). It's the same one-time provisioning category as cast server add, which already earned CLI status.

🤖 Generated with Claude Code

## Motivation The register script's env-var-pile interface produced three real footguns in one live provisioning run (incubator prod-migration, 2026-07-12): 1. **`APP_NAME` silently decouples from state.** The value must equal `github_apps.<repo>` in `environments.yaml` or every later `cast apply` fails to resolve the source — but the script takes it as a free-form env var and can't know it's wrong. The operator passed the GitHub App's display name instead of the state value; nothing caught it. 2. **Path/cwd fragility.** `CAST_STATE=.` + a relative `PRIVATE_KEY_FILE` means running from the wrong directory fails (or worse, half-works) depending on cwd. A typo'd script path (`~/local/…` vs `~/.local/…`) added a round of confusion the CLI itself would never have. 3. **`WEBHOOK_SECRET` is required even when the App's webhook is inactive** — the correct configuration for a tailnet-only Coolify where deliveries can never arrive and deploys are CI-triggered. Operators must invent a placeholder (`openssl rand -hex 16`) to satisfy the check. ## Proposal ``` cast github-app register <repo> \ --state <dir> \ --org <org-or-user> \ --app-id <id> --installation-id <id> \ --client-id <id> --client-secret-stdin \ --private-key <path.pem> \ [--webhook-secret <value>] # default: generated placeholder, noted in output ``` - **Validates against state:** resolves the Coolify-facing name from `github_apps.<repo>` in `environments.yaml` instead of accepting a free-form name — the footgun disappears structurally. - `--client-secret-stdin` (mirroring `docker login --password-stdin`) keeps the secret out of shell history and process listings; the current script takes it as an env var on the command line. - `--webhook-secret` optional; when omitted, generate a random value and say so (webhook-inactive Apps are a legitimate, arguably preferable, configuration for clone-only use). - Same API calls as the script (upload key → create github-app record); the script can stay as a thin wrapper or be removed. ## Scope note This stays a bootstrap-time, operator-run act — it doesn't blur the apply/diff reconciliation contract (semantics.md). It's the same one-time provisioning category as `cast server add`, which already earned CLI status. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
dan-claude-bot commented 2026-07-12 12:36:45 +00:00 (Migrated from github.com)

Subsumed by #7. The register subcommand proposed here stays exactly as specced — #7 keeps it as cast github-app register — but it lands as the floor that cast github-app create falls onto, rather than as a standalone wrapper around the env-var script.

Worth noting why, since it reframes the three footguns above: they are symptoms of the tool transcribing values a human read off a web page. The App Manifest flow lets cast obtain those values instead (it is the only programmatic way to create a GitHub App — there is no REST endpoint, and gh has no app subcommand). Footgun 1 dissolves because the Coolify-facing name is read from state rather than accepted as a flag; footgun 3 dissolves because the conversion response carries a real webhook_secret, so nobody has to invent a placeholder.

Leaving this open as the register-side spec; close it in favour of #7 if you'd rather track one issue.

Subsumed by #7. The register subcommand proposed here stays exactly as specced — #7 keeps it as `cast github-app register` — but it lands as the floor that `cast github-app create` falls onto, rather than as a standalone wrapper around the env-var script. Worth noting *why*, since it reframes the three footguns above: they are symptoms of the tool transcribing values a human read off a web page. The App Manifest flow lets cast **obtain** those values instead (it is the only programmatic way to create a GitHub App — there is no REST endpoint, and `gh` has no `app` subcommand). Footgun 1 dissolves because the Coolify-facing name is read from state rather than accepted as a flag; footgun 3 dissolves because the conversion response carries a real `webhook_secret`, so nobody has to invent a placeholder. Leaving this open as the register-side spec; close it in favour of #7 if you'd rather track one issue.
dan-claude-bot commented 2026-07-19 19:37:08 +00:00 (Migrated from github.com)

Closing in favour of #7, taking up the invitation in the comment above ("close it in favour of #7 if you'd rather track one issue").

Nothing in this spec is dropped. #7 keeps the register subcommand exactly as specced here — cast github-app register — as the floor that cast github-app create falls onto. The three footguns catalogued here are the motivation #7 inherits, and two of them dissolve structurally under the manifest flow rather than needing to be fixed by hand:

  • footgun 1 (APP_NAME decoupled from state) — the Coolify-facing name is read from github_apps.<repo> instead of accepted as a flag
  • footgun 3 (WEBHOOK_SECRET required for a webhook-inactive App) — the conversion response carries a real webhook_secret, so nobody invents a placeholder

Footgun 2 (path/cwd fragility) stays a plain CLI-ergonomics item under #7.

The working path is unaffected meanwhile: scripts/register-github-app.sh still does the job, with the APP_NAME-must-match-state constraint documented in its header.

Closing in favour of #7, taking up the invitation in the comment above ("close it in favour of #7 if you'd rather track one issue"). Nothing in this spec is dropped. #7 keeps the register subcommand exactly as specced here — `cast github-app register` — as the floor that `cast github-app create` falls onto. The three footguns catalogued here are the motivation #7 inherits, and two of them dissolve structurally under the manifest flow rather than needing to be fixed by hand: - footgun 1 (`APP_NAME` decoupled from state) — the Coolify-facing name is read from `github_apps.<repo>` instead of accepted as a flag - footgun 3 (`WEBHOOK_SECRET` required for a webhook-inactive App) — the conversion response carries a real `webhook_secret`, so nobody invents a placeholder Footgun 2 (path/cwd fragility) stays a plain CLI-ergonomics item under #7. The working path is unaffected meanwhile: `scripts/register-github-app.sh` still does the job, with the `APP_NAME`-must-match-state constraint documented in its header.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/cast#5
No description provided.