Skip to content

feat: deploy on merge to main with GitHub OIDC instead of a stored refresh token - #147

Open
dawsontoth wants to merge 12 commits into
mainfrom
feat/oidc-deploy-on-merge
Open

dawsontoth wants to merge 12 commits into
mainfrom
feat/oidc-deploy-on-merge

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

⊙ Problem

create-harper projects deploy on version tags with a refresh token stored as a GitHub secret, which someone has to mint and paste in, and which expires. Closes #144. Closes #145. Part of #143.

❓ Your call: should this wait for Harper 5.4.0?

deploy:setup-ci runs core Harper's new setup command (HarperFast/harper#3127), at the maintainer's direction, instead of #145's script in every template. On an earlier Harper it fails with an unknown provider, so this shouldn't merge before 5.4.0 ships. The alternative was shipping manual setup steps now.

💡 Solution

  • Every template's workflow tests pull requests and deploys when a change merges to main. The deploy job stores no credential: its harper commands authenticate with GitHub's OIDC token.
  • npm run deploy:setup-ci sets the cluster up once.
  • The deploy waits for every node to take the release, and the run summary names the deployment and its certification.
  • The deploy user can deploy any app on the cluster (Scope deploy permissions to component names harper#3046), so the README says to protect main.

⚖️ Alternatives

  • Deploy by reference (by_ref=true) — needs a clone credential on the cluster and can't carry a build's output, which the Next.js templates need.
  • Keep the refresh-token secret and only change the trigger — keeps a stored, expiring credential, and a user has one refresh token, so CI would need its own user.

❓ Your call: is a re-run guard enough to keep an older commit from deploying last?

Runs on main go one at a time, and a re-run refuses a commit that is no longer the tip. GitHub doesn't promise push order within the group, so two pushes arriving out of order could still deploy the older one last. A stricter guard brought back a case where nothing deploys.

❓ Your call: is a wait that can miss the job acceptable for now?

The rolling job lives only on the node that received the deploy. If the wait's polls never reach that node through a load balancer, the step fails with the outcome unknown, never a false success. HarperFast/harper#3060 fixes this by recording each node's outcome on the replicated deployment record.

🔧 Changes

✅ Verification

What the tests and the end-to-end run cover

🤖 Generated with Claude Code

Related PRs: #146 overlaps (it set HARPER_CLI_TARGET in the scaffolded .env; this comments out the placeholder it writes when no URL is given)
Complexity: medium

Review-Coverage: authored=claude; ran=gemini,codex,cursor-grok,cursor-muse; adjudicated=domain; declined=cursor-composer,cursor-kimi; rounds=5; full=2 @ cfa5b75

Review-Attention: study ~13m (decisions: payload-over-by-reference, guard-on-rerun-only, cluster-wide-deploy-user, app-prefix-for-json-literals, comment-out-placeholder, dotenv-runtime-dependency, do-less-alternative) @ cfa5b75

dawsontoth and others added 6 commits October 8, 2026 16:54
…fresh token

Every template's workflow now tests pull requests and deploys when a change
merges to `main` (or on Run workflow), instead of on a pushed `v*` tag:

- only the deploy job may request an identity token (`id-token: write`), in the
  `production` environment; HARPER_CLI_TARGET is a repository variable, and the
  step fails if it is empty rather than falling back to a saved login
- it uploads the checkout (`harper deploy project=<name> restart=rolling`),
  with `persist-credentials: false` so the job's token is not in the uploaded
  `.git`, and waits for the rolling job: a missing job id fails, a failed poll
  is retried, and a job never found is reported as an unknown outcome
- the run summary names the deployment, its certification, and how to go back
- Next.js keeps its own copy, which builds with `next build` before uploading
- checkout and setup-node move to the v7 pins this repository's CI uses

`deploy:setup-ci` (`harper deploy setup=true provider=github-actions`, Harper
5.4+) creates the trust policy, the deploy-only user and the repository
variable, so no setup script is copied into projects. `deploy` becomes the same
payload deploy for a manual run, and `deploy:setup` remains for private npm
registries. Each template's README Deployment section explains how the
workflow's `harper` commands authenticate, the one-time setup, certification,
going back by deployment_id, staging, refused runs and migration. The root
README, `--help` and the closing message point at the same steps.

template.test.js now runs in the default suite, and the Studio template build
drops the whole Deployment section instead of only its first paragraph.

Closes #144
Closes #145

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… reaches shell commands

From the pre-push review:
- the deploy job skips a run whose commit is no longer the head of main, so a
  run whose tests finished late can't deploy over a newer release
- package scripts and workflows use a new your-component-name-here placeholder,
  the project name reduced to [A-Za-z0-9_-], so a directory named `my app` or
  `a; b` can't split arguments or run a second command
- the summary says when the rollout did not complete on every node
- the README's migration steps install the workflow before the old secret is
  removed, and say why it can go; a manual deploy returns before the rollout
  finishes; `deploy:setup` is dropped, since payload deploys need no clone
  credential and the README sets up a private registry directly
- --help names the deploy:setup-ci script without assuming npm

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s with workflow concurrency

From the second pre-push review:
- the component name comes from the scaffolded directory's name, as
  `harper deploy` derives one, instead of the project-name argument, which is
  `.` for `create-harper .` and made every such project deploy as `app`
- workflow-level concurrency per ref replaces the stale-run guard: runs on main
  go one at a time, so an older commit can't deploy after a newer one, without
  the guard's case where a newer commit's failed tests left nothing deployed
- the migration steps say to scaffold with the project's own package manager
  and to reuse the copied workflow's project= value; the root README points
  older projects at the same steps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ch tip

Re-running failed jobs keeps the run's original commit, so after a newer release
deployed it would replace that release with an older one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d name a failed deploy

The harper CLI JSON-parses each key=value argument, so a project directory named
`2026` or `true` would send a number or boolean as the project. Such names now
get an `app-` prefix. The run summary says "failed" when the deploy returned no
deployment, rather than implying a partial rollout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the template deployment workflows to a modern, OIDC-based GitHub Actions deployment model, eliminating the need for stored Harper credentials in GitHub. It introduces a new deploy:setup-ci script across templates, sanitizes project directory names into valid Harper component names, and updates the scaffolding process to guide users through the new deployment setup. Feedback is provided regarding a fragile regular expression used to strip the deployment section in Studio templates, which could fail if it is the last section in the README.

Comment thread templates-studio/buildStudioTemplates.js Outdated
… checkout has CRLF

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…LF line endings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dawsontoth and others added 3 commits October 12, 2026 15:50
…tes' dependencies

The vanilla templates' config.yaml loads @harperfast/schema-codegen, but
their package.json listed it under devDependencies. The cluster installs a
deployed app's dependencies itself and omits devDependencies, so the
package was missing there: a 5.4 cluster's canary rejected the first
deploy of a fresh vanilla project ("Unable to find package
@harperfast/schema-codegen"). The react and vue templates already list it
under dependencies.

A test now checks that every package a template's config.yaml loads is in
its dependencies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no cluster URL given, the scaffolded .env set HARPER_CLI_TARGET to a
placeholder. The harper CLI reads .env ahead of the target `harper login`
saves, so every command in the project, `deploy:setup-ci` and `deploy`
included, went to a host that doesn't exist.

The target line is now written commented out, so it still shows where the
URL goes. `harper login` run in the project then adds the real
HARPER_CLI_TARGET to .env, since the file sets none. Appending to an
existing .env that already sets a target leaves the line out, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`deploy:setup-ci` now also grants registration_info, the version check
`harper deploy` makes before it uploads (HarperFast/harper#3127).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Reviewed with Codex

LAST="could not be read"
for attempt in $(seq 60); do
sleep 10
harper get_job id="$JOB_ID" json=true > "$RUNNER_TEMP/job.json" || continue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

major — polling can miss the node-local rollout job.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Known limitation, kept on purpose: CI uses the single cluster URL, and per-node URLs would need a trust policy for each node. The job record only exists on the node that received the deploy. If the polls never reach that node, the step fails, reporting that the job's outcome is unknown, and never reports success it can't see. The fix belongs in core: HarperFast/harper#3060 records each node's outcome on the replicated deployment record, and the workflow will switch to polling get_deployment when it lands. Polling through the CLI (HarperFast/harper#3058) would hit the same problem unless it streams the deploy or reads that record.

🤖 Posted by Claude Code

Comment thread template-react/README.md Outdated
…<id>

Pasted into a shell, `deployment_id=<id>` reads `<id>` as input
redirection from a file named id. The READMEs and the workflow summary now
use DEPLOYMENT_ID, and the README says to put the deployment's id there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deploy:setup-ci: create the cluster's trust policy and deploy role once Deploy on merge to main with GitHub OIDC instead of a stored refresh token

2 participants