Publish from GitHub with Curvenote Actions
For your coding agent¶
Using a coding agent such as Claude Code, Codex or Cursor? Copy this prompt into it from the root of your repository, or download the prompt. The agent interviews you to choose between the single article and multiple articles configurations. It then writes the workflow files and checks your myst.yml IDs. Finally, it leaves an AGENTS.md section that explains how to maintain the setup. It does not push or change repository settings; it ends with a checklist of the steps only you can do, such as adding the CURVENOTE_TOKEN secret.
# Set up Curvenote publishing with GitHub Actions
You are helping the user connect this Git repository to a Curvenote site so that MyST content is previewed, checked and published automatically using the reusable workflows in `curvenote/actions`. Interview the user, agree a configuration, then write the files. This prompt is published in the Curvenote docs (CLI → Automation → GitHub Actions, "For your coding agent"); the workflow reference is the README at <https://github.com/curvenote/actions>.
## Ground rules
- Work only in the local working tree. Do not push, open pull requests, create repositories, change repository settings, or add secrets yourself. List those as steps for the user.
- Inspect before asking. Do not ask for what you can read from the repository; confirm it instead ("Your preview workflow uses venue `opensci`, kind `Article`. Keep those?").
- Ask one or two questions at a time: first the configuration (section 2) and branch, then section 3 in order. Explain each choice in a sentence of plain language and propose a default. Accept "use the defaults"; for "not sure", apply your recommended default and say so.
- Never invent the venue or kind. If the user does not know them, write a quoted placeholder (`'<VENUE>'`, `'<KIND>'`) and list it as an open item. If the user does not know the collection, omit `collection` (the site's default collection is used) and list it as an open item.
- Use values exactly as the site defines them. `kind` and `collection` are case-sensitive, so keep existing values as written.
- Before writing files, show the planned file list (including `AGENTS.md`, and `CLAUDE.md` if touched) and the key settings, and get a yes. If `publish: true` will be set, say plainly that merging these workflows will publish the current content without review.
## 1. Inspect the repository
Find:
- every `myst.yml` (and legacy `curvenote.yml`) and its `project.id`;
- folders that look like one article per subfolder (e.g. `papers/*`), and any template or example folders;
- the default branch: try `git symbolic-ref --short refs/remotes/origin/HEAD`, otherwise use the current branch and confirm it with the user;
- existing `.github/workflows/*`, especially any that already use `curvenote/actions`;
- existing `AGENTS.md` and `CLAUDE.md`.
Summarize what you found in three or four lines.
## 2. Explain the two configurations and let the user choose
**A. Single article.** The repository is one paper (one `myst.yml`, at the root or in one folder). Every pull request gets a preview build and Curvenote checks, reported in a PR comment. Every merge to the default branch submits a new version of the same submission (matched by `project.id`). Optionally that version is published immediately, with no editor review.
**B. Multiple articles (proceedings / journal monorepo).** One nominated folder (e.g. `papers/`) holds one article per subfolder, each with its own `myst.yml`. Each pull request should touch a single article folder; PRs are often from forks. Maintainers drive the process with PR labels: one label (e.g. `draft`) builds previews and checks, and another (e.g. `approved`) submits the article. An optional manual workflow republishes every article at once. This is the pattern used by `scipy-conference/scipy_proceedings`.
If the repository shape clearly fits one configuration, recommend it and say why. If Curvenote workflows already exist, say which configuration they implement and ask what the user wants to change; then ask only about settings affected by that change, and edit only what is needed.
## 3. Collect settings (in this order)
1. **Site:** `venue` (the Curvenote site name, e.g. `scipy`), `kind` (the submission kind, e.g. `article`), and `collection` only if it is not the site's default (quote numbers: `'2026'`). Empty repository: also ask for the article title.
2. **Configuration A:**
- `path` — omit if `myst.yml` is at the root, otherwise the folder (e.g. `path: manuscript`).
- `publish: true` on merge? Only if the token's user may publish and everyone who can merge is trusted. Default: off (editors publish in Curvenote).
- Forks? Default no, which uses `pull_request`. If yes, the preview uses `pull_request_target` with a `draft` label gate (template A-fork below). The submit-on-merge workflow is unchanged.
3. **Configuration B:**
- Article folder → `path: <folder>/*`. Only immediate subfolders are used, and names starting with `.` or `_` are skipped automatically.
- Other folders to skip → `exclude:` as repository-relative paths, comma-separated (e.g. `papers/example, papers/old`). Not needed for `_`-prefixed folders.
- ID convention → `id-pattern-regex`. A regex can enforce a prefix, not that the ID equals the folder name, so state any naming rule in the template and `AGENTS.md`. If existing IDs share a prefix, match it. Otherwise recommend `^<venue>-<collection>-[a-zA-Z0-9_-]{3,40}$` (drop `<collection>-` if there is none); SciPy uses `^scipy-2026-(?:[a-zA-Z0-9-_]{3,25})$`. IDs may contain only letters, digits, `-` and `_`, and must be unique.
- Trigger style:
- **Labels (default, needed for forks):** preview label `draft`, submit label `approved`.
- **Merge means accepted (no forks):** preview on every PR (`pull_request`, no label), and submit on push to the default branch. Only changed article folders are submitted. Fork PRs get no preview with `pull_request`, because secrets are withheld.
- `enforce-single-folder: true` (recommended on PR workflows): fail if a PR touches more than one article, or files outside the article folder.
- A manual "publish all" workflow? It publishes every article immediately, with no review step, when someone runs it. Default yes for proceedings; skip it if the user does not want anything to publish without review.
4. **Extras:** mention them in one line and skip unless the user wants one.
- `strict: true` — don't submit if checks fail.
- `fonts` — Debian font packages for Typst PDFs.
- `typst: false` with `images: false` — slim image: faster, but no PDF or image tooling.
- `debug: true`.
- `comment: false` — no PR comment, so `pull-requests: write` can be dropped.
- `comment-title` — needed only when one workflow file calls draft or submit more than once. Each such job needs a distinct title, and no title may be a prefix of another.
## 4. Write the workflows
Rules for every workflow:
- Pin `@v1`. Replace `main` in the templates with the confirmed target branch.
- Always pass the secrets as `CURVENOTE: ${{ secrets.CURVENOTE_TOKEN }}` and `GITHUB: ${{ secrets.GITHUB_TOKEN }}`.
- Permissions:
- PR-triggered (preview or submit): `contents: read` and `pull-requests: write`.
- Submit on push or `workflow_dispatch`: `contents: write`, because it comments on the commit.
- Never set `label:` on a workflow that is not triggered by a pull request. Without a PR there are no labels, so it would never run.
- Update existing Curvenote workflows in place, keeping their file names, instead of adding duplicates. New files use the names below.
**A — `.github/workflows/curvenote-preview.yml`**
```yaml
name: Curvenote preview
on:
pull_request:
branches: ['main']
permissions:
contents: read
pull-requests: write
jobs:
preview:
uses: curvenote/actions/.github/workflows/draft.yml@v1
with:
venue: '<VENUE>'
kind: '<KIND>'
# path: manuscript # if myst.yml is not at the root
secrets:
CURVENOTE: ${{ secrets.CURVENOTE_TOKEN }}
GITHUB: ${{ secrets.GITHUB_TOKEN }}
```
**A-fork:** as above, but trigger on `pull_request_target`:
```yaml
on:
pull_request_target:
branches: ['main']
types: [labeled, opened, synchronize, reopened]
```
Then add `label: draft` under `with:`. When `id-pattern-regex` is needed, set it in every workflow.
**A — `.github/workflows/curvenote-submit.yml`**
```yaml
name: Curvenote submit
on:
push:
branches: ['main']
permissions:
contents: write
jobs:
submit:
uses: curvenote/actions/.github/workflows/submit.yml@v1
with:
venue: '<VENUE>'
kind: '<KIND>'
# publish: true # only if chosen
secrets:
CURVENOTE: ${{ secrets.CURVENOTE_TOKEN }}
GITHUB: ${{ secrets.GITHUB_TOKEN }}
```
**B — `.github/workflows/curvenote-preview.yml`**
```yaml
name: Curvenote preview
on:
pull_request_target:
branches: ['main']
types: [labeled, opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
preview:
uses: curvenote/actions/.github/workflows/draft.yml@v1
with:
venue: '<VENUE>'
collection: '<COLLECTION>'
kind: '<KIND>'
path: papers/*
id-pattern-regex: '^<venue>-<collection>-[a-zA-Z0-9_-]{3,40}$'
enforce-single-folder: true
label: draft
secrets:
CURVENOTE: ${{ secrets.CURVENOTE_TOKEN }}
GITHUB: ${{ secrets.GITHUB_TOKEN }}
```
**B — `.github/workflows/curvenote-submit.yml`:** identical, but uses `submit.yml@v1` and `label: approved`.
**B, merge-means-accepted:** the preview uses `pull_request` and no `label`. The submit is triggered `on: push` to the default branch, with `permissions: contents: write`, the same `with:` values, and no `label` or `enforce-single-folder`.
**B — `.github/workflows/curvenote-publish-all.yml`** (optional):
- `on: workflow_dispatch` and `permissions: contents: write`;
- uses `submit.yml@v1`;
- the same venue, collection, kind, path, exclude and `id-pattern-regex`;
- plus `include-unchanged: true` and `publish: true`;
- no `label` and no `enforce-single-folder`.
## 5. Make the content match
- Every folder the workflows build needs a `myst.yml` with a `project.id` that matches the pattern. IDs are validated only for folders a run builds, i.e. folders changed by the PR or push, or all folders with `include-unchanged`. A missing ID fails only the PRs that touch that folder. Excluded and `_`-prefixed folders are never built.
- The default pattern, used when `id-pattern-regex` is not set, is `^([a-zA-Z0-9-]{36})$`: a UUID, e.g. from `uuidgen | tr A-Z a-z`. For an empty repository, create a minimal `myst.yml` (`version: 1`, `project.id`, `project.title`).
- Never change an existing `project.id` without asking: the ID links the folder to its existing Curvenote submission. If a single article already has a non-UUID ID, set `id-pattern-regex` to accept it (e.g. `'^kerr-lab-ms-2026$'`).
- B: add missing IDs only if the user agrees, following the convention. Give the template an example ID and a comment explaining how authors should name their folder and ID. Do not otherwise restructure existing configs; for example, `extends:` is optional.
## 6. Leave an AGENTS.md section
Create `AGENTS.md` at the repository root, or update it if it exists. Write the content between `<!-- curvenote-actions:start -->` and `<!-- curvenote-actions:end -->`. If those markers already exist, replace only what is between them, and never remove other content. If `CLAUDE.md` exists and does not mention `AGENTS.md`, append the line `See AGENTS.md for Curvenote publishing automation.`
The section must state, briefly:
- the configuration (A or B), each workflow file and what triggers it;
- venue, collection, kind, path, exclude, the ID pattern (and, for A, the ID itself), and each label and what it does;
- how to add a new article (B) or cut a new version (A);
- that `project.id` must never change once submitted;
- if `publish: true` is used anywhere, that it publishes without review;
- for `pull_request_target` workflows: they run with secrets on code from forks, so maintainers should review the diff before adding a label. New commits to a labelled PR re-run automatically. Edits to these workflows take effect only once merged to the target branch;
- that `CURVENOTE_TOKEN` is a repository secret holding a Curvenote API token for a user on the site, and that the workflows pin `curvenote/actions@v1`;
- where to read more: <https://github.com/curvenote/actions> and the Curvenote docs page CLI → Automation → GitHub Actions. To change the setup, re-run the prompt "Set up Curvenote publishing with GitHub Actions" from that page.
## 7. Verify and hand over
- Parse every YAML file you wrote with any YAML parser, e.g. `python3 -c 'import yaml,sys;[yaml.safe_load(open(f)) for f in sys.argv[1:]]' <files>`, or run `actionlint` if it is installed.
- Confirm that each `path` and `exclude` folder exists, and that each built `myst.yml` ID matches its pattern and is unique.
- Run `grep -rn "'<[A-Z]*>'" .github` to list any placeholders left.
Finish with a short summary of the files you changed, then a checklist containing only the items that apply:
1. **Token:** create a Curvenote API token (Curvenote account menu → API tokens) for a user on the site's team, with rights to submit, and to publish if `publish: true`. If `CURVENOTE_TOKEN` already exists, check that its user has these rights.
2. **Secret:** add the token as a repository secret named `CURVENOTE_TOKEN` (Settings → Secrets and variables → Actions, or `gh secret set CURVENOTE_TOKEN`).
3. **Labels:** create the labels used, e.g. `gh label create draft` and `gh label create approved`. Only people with triage access can apply them, and they should review fork PRs before labelling.
4. **Placeholders:** fill in the placeholder values and open items listed above.
5. **Test:** commit, push and open a test PR.
- Configurations without labels: expect a "Curvenote Preview" comment with build and check links.
- Label-gated configurations: merge the workflows first, then open a PR touching one article and add the preview label.
- Note that any submit-on-push workflow also runs on the merge that adds it, so the current content is submitted at that point.
- Empty or new repository: create the GitHub repository and push the default branch first; the workflows run only on GitHub.
What the actions do¶
Curvenote publishes reusable GitHub workflows in curvenote/actions. They run the Curvenote CLI for you on GitHub’s servers. Your repository holds MyST content, and the workflows connect it to a Curvenote site:
draft.ymlbuilds a preview of your content and runs the site’s checks. The results are posted as a comment on the pull request, linking to the preview and the check report. Nothing is sent to the site’s editors.submit.ymlmakes a submission to the site, or a new version of an existing one, so it appears in the site’s submissions for editors to review. It can optionally publish immediately.push.ymluploads a work to Curvenote without submitting it. It can also set a site’s landing content. See the reference.
A submission is matched to your content by the project.id in myst.yml. Every submit with the same ID adds a new version to the same submission, so keep that ID stable once you have submitted.
Choose a configuration¶
Most repositories fit one of two configurations:
One repository is one paper. Pull requests get previews and checks; every merge to main submits a new version, and can publish it.
One folder holds an article per subfolder, as in conference proceedings. Maintainers use PR labels to preview, submit and publish each article.
Every option is listed in the Curvenote Actions reference.
Before you start¶
You need four things:
A Curvenote site to publish to. You need its name (the
venue, e.g.scipy), the submissionkindto use (e.g.article), and possibly acollection. Kinds and collections are set up by the site’s administrators in Curvenote Sites; ask them if you are unsure.A Curvenote API token for a user on the site’s team, with permission to submit (and to publish, if you want automatic publishing). Create one from the account menu (API tokens in Curvenote SCMS); Authentication covers tokens for the CLI.
A repository secret named
CURVENOTE_TOKENholding that token. In GitHub, go to Settings → Secrets and variables → Actions → New repository secret, or run:gh secret set CURVENOTE_TOKENMyST content with a
myst.ymland aproject.idin each article folder.curvenote initcreates one; see CLI commands.
Before setting up automation, you can try the same steps locally. Run curvenote check <venue> --kind <kind> and curvenote submit <venue> --kind <kind> --draft from your project folder.
- API
- Application Programming Interface
- CLI
- Command Line Interface
- ID
- Identifier
- MyST
- Markedly Structured Text
- SCMS
- Scientific Content Management System