Commit Graph
1190 Commits
Author SHA1 Message Date
Garrett Delfosse b1ead5f085 fix: set git identity for release tagging and surface git stderr (#26945)
## What happened

The [Tag and Release
run](https://github.com/coder/coder/actions/runs/28549109434/job/84641825784)
failed in the `prepare-release` job at the step "Prepare release
(calculate version, create tag and branch)" with:

```
error: create tag v2.35.0-rc.0: exit status 128
```

## Root cause

`prepare-release` creates an **annotated** tag via `git tag -a`
(`scripts/release-action/prepare.go`), which records a tagger and
therefore requires a git identity. The job never ran `git config
user.name/user.email`, and runners have none configured, so git aborts
with exit status 128. The real `fatal:` message was hidden because
`realExecutor.RunMutation` discarded the command's stderr.

## Changes

- **`.github/workflows/tag-and-release.yaml`**: add a "Configure git
identity" step (`ci@coder.com` / `Coder CI`) to the `prepare-release`
job, before the release tool runs. This matches the identity pattern
already used later in the same workflow.
- **`scripts/release-action/cmdexec.go`**: capture stderr in
`RunMutation` and include it in the returned error, so a failing
mutation surfaces the underlying command output (e.g. git's `fatal:`
line) instead of only `exit status N`.
- **`scripts/release-action/cmdexec_test.go`**: add a test asserting
stderr is surfaced on failure.

## Testing

- `go test ./scripts/release-action/...` passes.
- `go vet ./scripts/release-action/...` and `gofmt` clean.
- `actionlint .github/workflows/tag-and-release.yaml` clean.
- Reproduced the failure locally: `git tag -a` with no usable identity
exits 128 (`fatal: no email was given and auto-detection is disabled`);
with an identity configured it succeeds.

<details>
<summary>Root-cause analysis / decision log</summary>

**Failing step** runs `go run ./scripts/release-action prepare-release
--type create-release-branch --ref main --commit cb1a87b…`.

1. The tool computes the next version `v2.35.0-rc.0` and calls
`createAndPushTag`, which runs `git tag -a v2.35.0-rc.0 -m "Release
v2.35.0-rc.0" <targetRef>` (`prepare.go:56`).
2. That git command exits **128**, wrapped as `error: create tag
v2.35.0-rc.0: exit status 128`.

**Why it's the identity, and not something else:**

- No `git config user.name/user.email` step exists in the
`prepare-release` job; the `setup-mise` action does not set it; and the
tool itself never sets an identity. Annotated tags require a tagger, so
`git tag -a` fails on runners whose auto-detected identity is bogus
(`…@runner.(none)`), which is rejected under git's strict identity
check.
- Not a pre-existing tag collision: no `v2.35.0*` tag exists on the
remote, and the code pre-checks for an existing tag (and would emit a
different "already exists" error).
- Not an unresolved ref: `targetRef` resolves to the provided commit
SHA, checked out at `fetch-depth: 0`.
- The log was unhelpful because `RunMutation` used `cmd.Run()` without
wiring git's stderr (`cmdexec.go`), discarding the `fatal:` line and
leaving only `exit status 128`. This PR fixes that too.
- The sibling `release.yaml` explicitly sets `git config
user.email/user.name` before its git mutations; that step was simply
missing from the newer `tag-and-release.yaml` `prepare-release` job.

</details>

---

> Generated by Coder Agents on behalf of @f0ssel.
2026-07-01 15:10:45 -07:00
Garrett Delfosse 5573190530 fix(.github/workflows): restore mise tool setup in tag-and-release (#26937)
The `tag-and-release` workflow fails at startup with `Can't find
'action.yml', 'action.yaml' or 'Dockerfile' under
.../.github/actions/setup-go`. #26422 reintroduced stale references to
the `./.github/actions/setup-go` and `./.github/actions/setup-node`
composite actions, both of which were removed in #25727 when CI migrated
shared tool setup to `mise`.

This replaces both with the `setup-mise` pattern already used elsewhere
in the same workflow. The `prepare-release` job now installs `go` via
`setup-mise` (dropping the old `use-cache: false`, since Go caching is
now opt-in through the `go-cache` action). The `update-docs` job
installs `node pnpm` via `setup-mise` and adds `pnpm-install` so
`scripts/update-release-calendar.sh` still has the dependencies it needs
for `make fmt/markdown`.

`actionlint` and the `pre-commit` hook pass locally.

<details>
<summary>Root cause and decision log</summary>

**Symptom:** `tag-and-release.yaml` references
`./.github/actions/setup-go`, but that directory has no
`action.yml`/`action.yaml`/`Dockerfile`.

**How it broke:**

- #25727 (`ci: refactor CI to use mise for shared tool setup`) deleted
`.github/actions/setup-go/action.yaml` and
`.github/actions/setup-node/action.yaml`, migrating every workflow to
`./.github/actions/setup-mise`.
- #26422 (`feat: add dry-run flag via CommandExecutor interface`)
rewrote `tag-and-release.yaml`. It adopted `setup-mise` in one job but
left two stale references: `setup-go` (prepare-release) and `setup-node`
(update-docs), likely a rebase/merge artifact.

**Scope check:** Swept every workflow for local-action references
pointing at missing directories. The only genuine misses were `setup-go`
and `setup-node`. `create-task-action` is an external action checked out
at runtime via `actions/checkout` (not a repo-local action), and
`embedded-pg-cache`/`test-cache` resolve to existing `download`/`upload`
subdirectories.

**Mapping decisions:**

- `setup-go` (`use-cache: false`) to `setup-mise` with `install-args:
"go"`. The old action also installed `gotestsum`/`mtimehash` and
pre-warmed modules, but `prepare-release` only runs `go run
./scripts/release-action`, so `go` alone is sufficient. Caching stays
off, matching the original `use-cache: false`.
- `setup-node` to `setup-mise` with `install-args: "node pnpm"` plus
`pnpm-install`. The old action provided node + pnpm and installed
`node_modules`; `make fmt/markdown` (invoked at the end of the calendar
script) needs `node_modules/.installed` and `pnpm exec
markdown-table-formatter`.
</details>

---
Generated by Coder Agents on behalf of @f0ssel.
2026-07-01 23:13:28 +02:00
Garrett Delfosse ff7e0bc193 feat: add dry-run flag via CommandExecutor interface (#26422)
## Summary

Adds a `--dry-run` capability to the `release-action` Go tool and
exposes it through a **new** manual workflow, `tag-and-release.yaml`,
without disturbing the existing `release.yaml` pipeline.

PR #25162 had rewritten `release.yaml` in place to be driven by
`scripts/release-action`, which changed its `workflow_dispatch` inputs
from `release_channel`/`release_notes`/`dry_run` to
`release_type`/`commit_sha`. That broke `scripts/releaser`, which
dispatches `release.yaml` with the original inputs. This PR restores
`release.yaml` and moves the Go-driven pipeline to its own workflow.

## Workflow layout after this PR

| Workflow | Trigger | Driven by | Purpose |
|---|---|---|---|
| `release.yaml` | `scripts/releaser` (`gh workflow run`) | legacy
inline shell | Existing pipeline, restored to pre-#25162 state |
| `tag-and-release.yaml` | Manual (Actions UI) |
`scripts/release-action` Go tool | New pipeline with `prepare-release` +
`dry_run` |

`release.yaml` is restored byte-for-byte to its pre-#25162 version, so
its inputs match what `scripts/releaser` sends again.

## `release-action` design

### CommandExecutor interface

Abstracts CLI command execution behind read-only and mutating methods:

| Method | Purpose | Dry-run behavior |
|---|---|---|
| `RunOutput` | Read-only, capture stdout | Executes normally |
| `Run` | Read-only, exit code only | Executes normally |
| `RunMutation` | Changes remote state, no output | **Prints command,
skips execution** |
| `RunMutationStdout` | Changes remote state, streaming I/O | **Prints
command, skips execution** |

Two implementations: `realExecutor` (executes via `os/exec`) and
`dryRunExecutor` (delegates read-only calls, prints mutating calls).

### `prepare-release` subcommand

Composes `calculateNextVersion` with idempotent tag and branch
creation+push, emitting the same JSON as `calculate-version`. Matching
existing refs are skipped; mismatched refs error.

### `tag-and-release.yaml` `dry_run` input

When enabled: `prepare-release` runs with `--dry-run` (version
calculated, plan printed, nothing pushed), notes are generated for
inspection, and the build+publish job is skipped via an `if` guard
(cascading to homebrew/winget/docs).

## Mutating commands covered by `--dry-run`

| Command | Call site |
|---|---|
| `git tag -a <version> ...` | `createAndPushTag` |
| `git push origin refs/tags/...` | `createAndPushTag` |
| `git push origin <sha>:refs/heads/...` | `createAndPushBranch` |
| `gh release create ...` | `publishRelease` |

`git fetch --tags --force origin` is intentionally not a mutation; it
only updates local remote-tracking refs and must run for accurate
version calculation.

## Changes

- **New**: `scripts/release-action/cmdexec.go`, `prepare.go` (+ tests)
- **Refactored**: `git.go`, `github.go`, `calculate.go`, `notes.go`,
`commit.go`, `publish.go` to thread `CommandExecutor`; added `gitMutate`
- **Updated**: `main.go` adds `--dry-run` flag and `prepare-release`
subcommand
- **New**: `.github/workflows/tag-and-release.yaml` (manual, Go-driven,
with `dry_run`)
- **Reverted**: `.github/workflows/release.yaml` to its pre-#25162 state

> [!NOTE]
> Generated by Coder Agents on behalf of @f0ssel
2026-07-01 16:20:00 -04:00
Jakub Domeracki 28447f16ea ci(.github/workflows): update checkout to v7 (#26909)
Update GitHub Actions workflows to use `actions/checkout` v7.0.0 pinned
to `9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0`, following the GitHub
Actions checkout hardening changes announced in:

-
https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/
-
https://github.blog/changelog/2026-06-18-control-who-and-what-triggers-github-actions-workflows/

Audited the existing `pull_request_target` workflows and did not add any
`allow-unsafe-pr-checkout` opt-outs, since these workflows do not
intentionally check out fork PR head code.

Generated by Coder Agents.

<details>
<summary>Plan notes</summary>

- Update all `.github/workflows` `actions/checkout` references to v7.0.0
using the pinned SHA.
- Preserve SHA pinning, including the newly added MCP registry workflow.
- Validate that old checkout pins are removed and no unsafe checkout
opt-outs are introduced.

</details>
2026-07-01 10:17:35 +02:00
Nick Vigilante 14a61041d9 docs: fix broken links in weekly-docs link check (#26813)
Fix four broken links that caused the weekly-docs link-check CI job to
fail.

**Changes:**

- `docs/install/rancher.md`: Remove `#readme` anchor from
`../../helm#readme` — linkspector splits on `#`, finds a directory, and
errors with EISDIR.
- `docs/install/kubernetes.md`: Same fix for `../../helm/coder#readme`.
- `docs/install/cloud/compute-engine.md`: Point both `gcp-linux` links
to `README.md` explicitly
(`../../../examples/templates/gcp-linux/README.md` and
`../../../examples/templates/gcp-linux/README.md#authentication`) so
linkspector can resolve the file and anchor.
- `.github/.linkspector.yml`: Add `merriam-webster.com` to
`ignorePatterns` (returns 403 from GitHub runner IPs).

<details>
<summary>Linear issue and CI context</summary>

**Linear issue:**
https://linear.app/codercom/issue/DOCS-494/fix-broken-links-in-weekly-docs-link-check

**Failing CI run:**
https://github.com/coder/coder/actions/runs/28366176335/job/84032582533

The workflow is `.github/workflows/weekly-docs.yaml`, job `check-docs`,
step `Check Markdown links` (umbrelladocs/action-linkspector).

Root causes confirmed per investigation:
- `#readme` anchors on directory paths trigger EISDIR in linkspector's
local resolver.
- The `gcp-linux` directory links needed explicit `README.md` targets;
linkspector cannot resolve bare directory references.
- `merriam-webster.com` blocks GitHub runner IPs with 403.

`ignorePatterns` is reserved for external links only, not internal or
GitHub file links.

</details>

---
*Generated by Coder Agents on behalf of @nickvigilante*
2026-06-29 13:12:19 -04:00
McKayla はな 1302e78283 ci: remove chromatic (#26777) 2026-06-29 08:57:07 -06:00
Atif AliandBen Potter 18efcb6c41 feat: publish Coder MCP server to official MCP Registry (#21673)
## Summary

This adds the necessary configuration to publish Coder's remote MCP
server to the official MCP Registry at registry.modelcontextprotocol.io.

## Changes

- **`server.json`**: MCP server metadata for registry discovery
- **`.github/workflows/publish-mcp-registry.yaml`**: GitHub Actions
workflow to automatically publish on release

## How it works

1. When a new Coder release is published, the workflow automatically
publishes to the MCP Registry
2. MCP clients (Claude, ChatGPT, VS Code, etc.) can discover Coder via
the registry
3. Users just need to provide their Coder deployment URL - OAuth handles
authentication automatically via RFC 7591 Dynamic Client Registration

## MCP Registry Entry

The server will be listed as `io.github.coder/coder` with:
- **Transport**: `streamable-http` 
- **Endpoint**: `{coder_url}/api/experimental/mcp/http`
- **Auth**: OAuth2 (automatic via
`/.well-known/oauth-authorization-server`)

## Testing

After merge and next release, verify at:
```bash
curl "https://registry.modelcontextprotocol.io/v0.1/servers?q=io.github.coder"
```

Closes #21275

---
_Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking:
`medium`_

---------

Co-authored-by: Ben Potter <me@bpmct.net>
2026-06-26 11:20:45 -05:00
Danny Kopping 9f211ce5ae fix: use HEAD instead of fetching base branch for emdash linter (#26733)
## Problem

The `lint/emdash` check fails on Graphite-stacked PRs. See [this failed
run](https://github.com/coder/coder/actions/runs/28225390084/job/83616080375?pr=26650):

```
Base ref origin/graphite-base/26650 not found locally, fetching graphite-base/26650...
ERROR: could not fetch base ref origin/graphite-base/26650.
ERROR: could not determine base ref.
make: *** [Makefile:768: lint/emdash] Error 1
```

`scripts/check_emdash.sh` resolved its diff base by fetching
`origin/$GITHUB_BASE_REF` and computing a merge-base. Graphite sets
`GITHUB_BASE_REF` to a `graphite-base/<n>` ref that is ephemeral (it is
not reliably present on origin), so the fetch fails and the check errors
out instead of running.

## Fix

`actions/checkout` checks out the PR **merge commit**
(`refs/pull/<n>/merge`), whose **first parent (`HEAD^1`) is the exact
base commit GitHub merged against**. Diffing `HEAD^1` against the
checkout yields every change the PR makes against its base branch, for
normal and Graphite-stacked PRs alike. No base-branch fetch, no
merge-base computation, no `gh`-based deepen dance.

- `scripts/check_emdash.sh`: use `HEAD^1` (the PR base commit) as the
diff base in CI. Drops `resolve_merge_base` and `fetch_base_ref`. Emits
a clear error if `HEAD^1` is missing (checkout too shallow).
- `.github/workflows/ci.yaml`: bump the `lint` job checkout to
`fetch-depth: 2` so `HEAD^1` is present with no runtime fetch.

Local dev behavior (merge-base against `origin/main`) is unchanged.

## Verification

- `make lint/emdash`, `make lint/shellcheck`, `make
lint/actions/actionlint` pass.
- Simulated the CI path with `GITHUB_BASE_REF` set: the check resolves
to `HEAD^1` without fetching and still flags an added line containing an
emdash.

<details>
<summary>Why the merge commit's first parent</summary>

For a `pull_request` checkout of `refs/pull/<n>/merge`:

- `HEAD` = GitHub's synthetic PR merge commit
- `HEAD^1` = the exact base commit used for the merge
- `HEAD^2` = the PR head commit

`git diff HEAD^1 HEAD` is the full-tree diff from the base snapshot to
the merged result, i.e. all of the PR's changes against its base. This
is immutable and always local (given depth >= 2), unlike base branch
refs which are mutable and, for Graphite stacks, ephemeral.

</details>

---

This PR was generated by Coder Agents on behalf of @dannykopping.
2026-06-26 12:34:49 +00:00
Nick Vigilante b95f2531b5 feat: populate docs prose style guide as a landing page plus subpages (#26632)
Replace the `docs/.style/style-guide.md` scaffold with the populated
prose style guide,
structured as a `README.md` landing page plus one subpage per topic so
GitHub auto-renders the landing when readers open the style-guide
folder.

## Layout

```text
docs/.style/
  style-guide/
    README.md                          (landing: intro, section list, editing conventions, Vale enforcement)
    audience-and-scope.md              (one audience, one outcome, declared up front; canonical personas)
    voice-and-tone.md
    word-choice.md
    accessibility-and-inclusion.md     (new)
    capitalization-and-punctuation.md
    formatting.md                      (text formatting + block elements + screenshots sparingly)
    numbers-units-and-dates.md
    editor-setup.md                    (placeholder)
```

Every repo reference to the old path is rewired to the new path:
`AGENTS.md` (and its `CLAUDE.md` / `.cursorrules` symlinks),
`.claude/docs/DOCS_STYLE_GUIDE.md`,
`docs/about/contributing/documentation.md`, `docs/.style/README.md`,
`docs/.style/styles/Coder/README.md`, and a comment in
`.github/workflows/ci.yaml`. The touched paragraph in each of those
files is reformatted to one sentence per line per the touch-paragraph
rule (refer to [Conventions the guide
dogfoods](#conventions-the-guide-dogfoods)).

## What each page covers

- **Audience and scope** (new): every page targets **one audience
working toward one outcome**; the **install-vs-deploy Coder example**
(workspace user vs platform engineer); pick one audience per page (write
two pages and cross-link rather than tagging sections); pick one outcome
per page (`Configure SSO with Okta` is one outcome, `Configure SSO` is
not); declare audience and scope up front (the H1 names the outcome; the
first paragraph names the audience); **canonical Coder personas**
inlined as four primary (Dave the Developer, Ada the Infrastructure
Admin, Perry the Platform Engineer, Steven the Sponsor) and six
secondary (Melissa the Machine Learner, Tommy the Tester, Caitlin the
Citizen Developer, Felipe the FinOps, Sergio the Security Officer, Tara
the Team Leader), each with a `Coder surface:` line covering the
relevant CLI/workspace/template/RBAC surfaces.
- **Voice and tone**: address the reader directly, avoid first-person
singular, reserve first-person plural for **Coder Technologies the
company** (with an explicit ban on `we` for the product itself and on
combined `you and the docs`), active voice, present tense with a
**conditional/predictive `will` exception** (`If you do X, Y will
happen`), **no sentence-ending prepositions** with a clunky-exception
note.
- **Word choice**: Coder product and feature names with the **Coder CLI
always in backticks (`coder`)** rule, brand names with a parallel
**Terraform CLI in backticks (`terraform`)** rule, **Dev Container**
terminology (proper-noun specification vs lowercase instance, parallel
to Coder / workspace), **phrasal verbs and their noun forms generalized
as a table** (set up/setup, log in/login, sign in/sign-in, log
out/logout, back up/backup, roll out/rollout, start up/startup, shut
down/shutdown, with the `Quickstart` exception), `refer to` / `check
out` / `visit` over `see`, `Learn more` versus `Next steps` with an
**ableism rationale** (`steps` as a physical-mobility metaphor),
`tutorial` versus `walkthrough` with an **ableism rationale**,
**`select` over `click`**, **`Don&#39;t assume simplicity or
difficulty`** (covers both `simple`/`easy` and `complex`/`non-trivial`),
**`Avoid weasel words`** (vague attributions in the Wikipedia sense like
`many believe`, `experts agree`, `studies show`), plain language for
product actions with an **industry-term exception scope** for the Linux
`kill` command, the `SIGKILL` signal, and the `disabled` config flag
state.
- **Accessibility and inclusion** (new): WCAG 2.1 Level AA as the
minimum target with AAA as a stretch goal; heading structure (one H1 per
page, no skipped levels, **substantive content between headings**);
inclusive pronouns; inclusive-language substitutions including a
**dedicated `sanity check` row** with `smoke testing` / `confidence
testing` / `acceptance testing` alternatives; descriptive link text; alt
text and decorative-image conventions; **plain English for international
readers** (no idioms; common Latin abbreviations `e.g.`, `i.e.`, `etc.`,
`vs.`, and `et al.` allowed, less common ones not); page descriptions in
`docs/manifest.json` (the docs site does not yet support YAML front
matter); reading level; color contrast deferred to the docs site theme.
- **Capitalization and punctuation**: sentence-case headings, no
gerund-leading headings with **documented exceptions** (`Pricing`,
`Billing`, `Logging`, `String formatting`, etc.), **trailing heading
punctuation in three tiers** (periods and exclamation marks forbidden at
error severity, question marks allowed sparingly at suggestion severity,
characters inside backticks exempt for both), no em or en-dashes with a
**corrected example** showing parenthetical em-dash use rather than
series-joining, Oxford comma, US-style quotation, semicolons sparingly,
rare exclamation marks, numeric ranges.
- **Formatting**: text formatting (bold for UI with **explicit
greater-than separator rule for navigation paths**, italics for
emphasis, code font for identifiers presented as a **bulleted list**)
and block elements (code blocks with language fences plus **link to the
Prism supported-languages reference**, callouts with tightened
scenarios, tabs with the actual `` syntax and a **macOS/Linux/Windows
example**, lists with a **five-item prose-list cap rule** and an
**explicit terminal-punctuation rule** (complete sentences end in
periods, phrases completing a lead-in paragraph end in periods,
single-word labels carry no terminal punctuation, no mixing styles in
one list), tables with a **narrow-table guideline** that reconsiders the
structure when many columns are needed, links including the rule that
**non-docs codebase links also use relative paths**, images,
**screenshots sparingly** with a maintenance-burden rationale and an
adapted quote from Lorna Jane Mitchell&#39;s `Short tech writing style
guide for developers`), with cross-references to the accessibility page
for link text and alt text.
- **Numbers, units, and dates**: digits everywhere preference,
non-breaking space between number and unit with **separate pre-render
(Markdown source) and post-render (visible output) demonstrations** plus
a **window-shrink tip** for confirming the rule visually, `Month Day,
Year` date format, 12-hour time with AM/PM, ordinals exception.
- **Editor setup**: placeholder.

## Conventions the guide dogfoods

- **One sentence per line**. Source lines follow a one-sentence-per-line
policy: each sentence sits on its own Markdown source line, sentences
are not split across lines, and lines do not wrap to a fixed column
width. The same convention applies corpus-wide through an **incremental
touch-paragraph rule**: when a contributor edits any line inside a
paragraph, the whole paragraph is reformatted to one sentence per line
as part of the same edit. Bullet items, numbered list entries, and
blockquote lines are each their own paragraph for the rule. Headings,
fenced code blocks, and tables are out of scope. `markdownlint`&#39;s
`MD013` is already disabled, so the convention is editorial.
- **No navigational `see`**. Replaced with **refer to** (formal
default), **check out** (informal/tutorials), or **visit** (external
URLs). `See` is reserved for the observational meaning.
- **HTML entities for em-dashes inside demos**. The em-dash demo encodes
`—` / `–` so the source stays ASCII while the rendered output still
shows the character.
- **No semicolons in body prose**. Body prose prefers two sentences over
a semicolon. Semicolons survive only in heading and rule labels where
they act as separators.
- **Common Latin abbreviations allowed in own prose**. `e.g.`, `i.e.`,
`etc.`, `vs.`, and `et al.` (citation contexts) are fine. Less common
Latin abbreviations (`a priori`, `q.v.`, `viz.`, `n.b.`, `cf.`, `ibid.`)
are not. The rule covers punctuation too: prefer parentheses around
`e.g.` and `i.e.` clauses, one period when `etc.` ends a sentence, both
periods when `etc.` ends a parenthetical that ends a sentence.
- **No idioms or industry-jargon idioms**. `deep dive`, `paved path`,
etc. are rewritten in plain language.

## Rule conventions

Each rule pairs a rationale with **Do** / **Don&#39;t** blockquoted
examples and a parenthetical noting the Vale rule that enforces (or will
enforce) the policy. Documentation-only rules are explicitly labeled as
such. Substitution rules use tables.

## Out of scope

- Wiring any new Vale rule. Per-rule PRs land separately per the
rule-authoring doctrine in `docs/.style/README.md`.
- Editor setup page population.
- Redirecting `docs/about/contributing/documentation.md` to the
populated guide (needs a coordinated `coder.com` PR after merge).
- Trimming the `Writing Style` block in
`.claude/docs/DOCS_STYLE_GUIDE.md` and removing the `currently a
scaffold` framing in the agent docs.
- A separate demo PR for the callout types rendered against an existing
docs page.
- Sweeping navigational `see` out of other docs files. The new rule only
dogfoods on the style guide itself; a corpus-wide sweep is a separate
ticket.

## Validation

- `make fmt/markdown`: clean.
- `make lint/markdown`: 0 errors across 494 files.
- `./scripts/check_emdash.sh`: clean.
- Pre-commit-light: passes (fmt + lint + emdash + shellcheck + typos +
actionlint + migrations + helm).
- Dogfood scan: no first-person singular in own prose, no idioms, only
the five allowed Latin abbreviations in own prose, no `walkthrough` or
`Next steps` outside rule definitions and examples, no navigational
`see`, no `click` outside rule definitions and examples, no semicolons
in body prose.

<details>
<summary>CI flake note: <code>check-docs</code> (linkspector)</summary>

The `check-docs` job can fail intermittently on pre-existing external
links in `docs/about/contributing/documentation.md` (lines 29 and 30):
Merriam-Webster occasionally returns HTTP 403 to GitHub Actions runners
and Chicago Manual of Style can time out at 30s. Neither link is touched
by this PR. `docs/.style/` itself is in `.github/.linkspector.yml`
`excludedDirs`, and linkspector annotations confirm zero broken links
from the new pages.

</details>

Resolves DOCS-434.

---

*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick&#39;s behalf.*
2026-06-25 21:11:39 +00:00
a11f349c16 docs: document log collection for Coder Desktop on macOS and Windows (#26631)
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: Atif Ali <atif@coder.com>
2026-06-23 19:22:34 +00:00
Nick Vigilante 6acf32701e fix: preserve Vale severity in CI annotations and add three-severity demo (#26587)
Closes DOCS-426. Follow-up to
[#26586](https://github.com/coder/coder/pull/26586) (DOCS-425, strip),
which merged first.

## Problem

The Vale problem matcher at `.github/vale-problem-matcher.json`
hard-codes `"severity": "warning"`. Every Vale finding renders as a
GitHub `warning` annotation, regardless of Vale's actual severity. Nick
observed this on PR [#25501](https://github.com/coder/coder/pull/25501):
error-level findings from `Coder.BrandNames` appear as warnings.

This collapsed the doctrine's three-severity ladder (`error` / `warning`
/ `suggestion`) into a single advisory channel for the reader of a PR
diff. This PR restores the ladder visually so contributors and reviewers
see each rule's intended severity.

## Root cause

GitHub Actions problem matchers expect either a regex capture group for
severity or a hard-coded severity. Vale's `--output=line` format
produces `path:line:col:rule:message` with severity stripped, so the
matcher had no severity to capture and fell back on the hard-coded
value.

## Fix

### Commit 1: severity rendering

Switch the Vale prose lint step to `vale --output=JSON` and pipe through
`jq` to emit GitHub workflow commands directly. Drop the problem matcher
file.

| Vale severity | GitHub workflow command |
|---|---|
| `suggestion` | `::notice::` |
| `warning` | `::warning::` |
| `error` | `::error::` |

Message bodies are URL-encoded for `%`, `\r`, and `\n` per the GitHub
Actions workflow command spec. The Vale step stays advisory
(`continue-on-error: true`, `vale --no-exit`); rendering becomes correct
but the step never fails the job.

### Commit 2: three-severity demo

Three throwaway `Coder.Demo*` rules at `level: suggestion`, `level:
warning`, and `level: error`, plus a
`docs/.style/_vale-annotation-demo.md` file that triggers each rule
exactly once. Together with the rendering fix above, this PR's CI
surfaces three GitHub annotations in three distinct severities (notice,
warning, error). Use the Files Changed view to inspect rendering.

The demo files live permanently in `docs/.style/`, which is excluded
from coder.com. They re-trigger annotations only on PRs that touch the
demo file itself, so they don't pollute CI on day-to-day PRs.

## Sample output

<img width="1443" height="1293" alt="image"
src="https://github.com/user-attachments/assets/fb337315-7b55-40b3-9983-828b2d5399fc"
/>

<img width="1443" height="1293" alt="image"
src="https://github.com/user-attachments/assets/b02d575d-5905-4c6d-b145-ad5df6e04f11"
/>

## Out of scope

Blocking merge on `error`-level findings is the natural next step but is
sequenced as the **final** step of the prose-style rollout. It was
prototyped in this PR (commit 3, since backed out) and verified
end-to-end against the demo doc. The work moved to
[DOCS-433](https://linear.app/codercom/issue/DOCS-433/block-merge-on-vale-error-level-findings-final-step-of-prose-style)
so the corpus of enabled rules is broad enough by the time the gate
lands that it catches real violations rather than novelty failures from
a single rule.

## Expected CI state on this PR

`lint-docs` passes. The three demo annotations render at lines 17 / 19 /
21 of `docs/.style/_vale-annotation-demo.md` as `::notice::`,
`::warning::`, and `::error::` respectively. The `::error::` annotation
does not fail the job because the Vale step is still advisory under this
PR.

Local verification of the rendering pipeline:

```
$ printf '%s\n' 'docs/.style/_vale-annotation-demo.md' \
    | xargs -d '\n' vale --no-exit --output=JSON \
    | jq -r '...'
::notice  file=docs/.style/_vale-annotation-demo.md,line=17,col=3,title=Coder.DemoSuggestion::[Demo] Suggestion-level Vale annotation.
::warning file=docs/.style/_vale-annotation-demo.md,line=19,col=3,title=Coder.DemoWarning::[Demo] Warning-level Vale annotation.
::error   file=docs/.style/_vale-annotation-demo.md,line=21,col=3,title=Coder.DemoError::[Demo] Error-level Vale annotation.
```

<details>
<summary>Decision log</summary>

- **Workflow commands vs custom Vale template + updated matcher**: chose
workflow commands because the transform is a 10-line jq pipeline with no
extra files to maintain, and it bypasses GitHub Actions problem-matcher
limitations entirely. The custom-template option would have kept the
matcher infrastructure but required an additional Go template file under
`.github/`.
- **Throwaway demo rules vs reusing existing rules**: chose throwaway
because we wanted each severity to fire deterministically from a single
unambiguous marker. Reusing existing rules would couple the demo to
corpus content and obscure the signal.
- **Demo persists vs drops before merge**: persists. The merge-gate
constraint that originally forced the demo to drop is gone (deferred to
DOCS-433). The four demo files live in `docs/.style/`, excluded from
coder.com, and only annotate PRs that touch them. They double as a
permanent canary so a future regression in severity rendering surfaces
immediately on whichever PR introduces it, and as the verification
artifact DOCS-433 uses when re-installing the merge gate.
- **`docs/.style/_vale-annotation-demo.md` filename**: underscore prefix
follows Coder convention for files that exist outside the normal docs
taxonomy. Not surfaced on coder.com/docs because `docs/.style/` is
excluded from the manifest, deploy workflow, and docs preview.
- **Merge-block deferred to DOCS-433**: the rendering fix and the merge
gate are independent changes. Shipping the rendering first lets
contributors see the three-severity ladder while the rule catalogue is
still small and the false-positive policy hasn't been stress-tested yet.
The gate lands as the final step of the rollout, after the catalogue is
broad enough that the gate covers real prose-style policy rather than
one rule's enforcement.

</details>

---
*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*
2026-06-23 12:55:15 -04:00
Nick Vigilante c13dd06d2f ci(.github/workflows): disable audit-docs-paths pending cross-repo auth (#26571)
The `audit-docs-paths` job in `weekly-docs.yaml` fetches a config file
from a private upstream source. An anonymous read returns 404 and the
job fails on every weekly run, firing a misleading "Stale docs paths
found in site/src/" Slack notification (a pre-existing bug in the
notification copy, tracked separately).

We originally tried to authenticate the fetch with the existing CI token
used for cross-repo work (the same one used by `contrib.yaml`), but that
token does not have read access to the upstream source. The proper fix
is a GitHub App scoped to cross-repo `Contents: Read`; the docs team is
tracking the App provisioning internally.

Until the App is provisioned, this PR disables the job behind a
`vars.AUDIT_DOCS_PATHS_ENABLED` repository variable. The variable is
unset, so the job skips on the weekly cron and on `workflow_dispatch`.
The other two jobs in this workflow (`prepare-linkspector-browser`,
`check-docs`) keep running normally, so docs PRs still get link-checked.

Re-enabling once the App is provisioned is a one-line change: set
`AUDIT_DOCS_PATHS_ENABLED` to `'true'` on this repo, no workflow edit
required.

<details>
<summary>Investigation log (why the App is needed)</summary>

Initial attempt (commits `5cca548`, `fc0ff59`, now discarded)
authenticated the fetch via the GitHub Contents API with `Accept:
application/vnd.github.raw` and an existing CI token already used for
cross-repo writes. `coder-agents-review` approved that approach in Round
2 ([review
4546533491](https://github.com/coder/coder/pull/26571#pullrequestreview-4546533491)),
and all 29 CI checks passed.

Validation via `workflow_dispatch` (run
[27973114839](https://github.com/coder/coder/actions/runs/27973114839))
failed at the fetch step with `curl: (22) The requested URL returned
error: 404`. The bare `curl` against the same URL with a personal access
token returned HTTP 200 and valid JSON, so the call shape was correct;
the CI token just lacks the necessary scope on the upstream source. The
Contents API returns 404 (not 403) when a token cannot see a private
repository, which is why the original failure mode was hard to
attribute.

Options considered:

1. **Extend the existing CI token** to include the missing read access.
Cheapest in lines of code, but the token is org-CI-owned and changing
its scope has blast radius beyond this job.
2. **New fine-grained PAT.** Tightest scope, but PATs are user-owned. If
the issuing user leaves the org, the token auto-revokes and the audit
silently breaks again, which is exactly the failure mode this PR is
trying to make less likely.
3. **GitHub App owned by the org.** Tied to the org, not a user;
survives staff turnover; least-privileged per repo. Heaviest setup
because creation, installation, and secret provisioning all need org
admin.

Option 3 is the right long-term answer but is not same-day. Disabling
the job is the smallest change that stops the noise immediately, and the
feature-flag variable keeps the re-enable path to one step.

</details>

<details>
<summary>Validation</summary>

* `actionlint` clean on `.github/workflows/weekly-docs.yaml`.
* Branch passed all 29 CI checks under the previous authentication
approach; this revision is strictly smaller (one job-level `if` guard +
comments), no new failure surface introduced.
* The disable cannot be tested end-to-end without merging, since the
affected job runs on `schedule` / `workflow_dispatch` against `main`.
Once merged: confirm the next weekly run (or a manual
`workflow_dispatch`) shows `audit-docs-paths` as skipped, with no Slack
notification.

</details>

---

> Generated by [Coder Agents](https://coder.com) on behalf of
@nickvigilante.
2026-06-22 15:45:41 -04:00
Nick Vigilante ee3572ab9a feat: wire Vale prose linter into docs CI (#25467)
Wires Vale into docs CI as an advisory (non-blocking) prose-lint step.
Closes [DOCS-40](https://linear.app/codercom/issue/DOCS-40).

> **Integration update (rebased onto `main`).** Since this branch was
opened, `main` consolidated docs linting into the **required**
`lint-docs` job in `ci.yaml` and removed the standalone `docs-ci.yaml`
([#25608](https://github.com/coder/coder/pull/25608)). This PR adds Vale
to that `lint-docs` job instead of resurrecting `docs-ci.yaml`, and the
`docs/.style/` scaffold defers to the merged
[#25466](https://github.com/coder/coder/pull/25466) (DOCS-180). Vale
stays advisory.

> **Post-review refactor.** Following the Coder Agents review, Vale is
now invoked through `mise exec "aqua:errata-ai/vale"` (the same pattern
as `actionlint`/`zizmor`) instead of a bespoke `curl`/`tar` download.
This removed the GNU-only `grep -oP` version extraction and `uname`/arch
mapping that broke on macOS BSD grep, and the prose step now skips paths
a PR deletes. See the resolved review threads for CRF-17/19/20/21/22.

A sample of what this check does is as follows:

<img width="1443" height="1293" alt="image"
src="https://github.com/user-attachments/assets/cf68dbf9-d9df-49ba-8dbf-200875bc289e"
/>

## What changes

- `.vale.ini` at the repo root: Google base + Coder (custom, empty in
v1) + curated write-good. `alex` rules are pulled in a la carte. Inline
comments justify every enable/disable.
- `mise.toml`: pin Vale `3.7.1` via aqua. `mise.lock`: lock that pin
across all platforms so `mise install --locked` (used by `build_image`)
resolves it.
- `Makefile`: a `docs/.style/.vale-synced` sentinel that gates `vale
sync`, and a `lint/prose` target that runs `vale --no-exit`. Both invoke
Vale via `mise exec "aqua:errata-ai/vale" -- vale ...`, so mise owns the
version and the OS/arch download (no hand-rolled install path).
- `.github/workflows/ci.yaml`: append Vale steps to the existing
required `lint-docs` job: `Detect changed Markdown`, `Restore Vale
styles`, `Prepare Vale styles` (`make docs/.style/.vale-synced`), `Vale
prose lint`, and a default-branch-only `Save Vale styles`. They lint
only changed Markdown under `docs/` that still exists on disk, with a
problem matcher for inline PR annotations.
- `.github/vale-problem-matcher.json`: parses `vale --output=line` so
alerts surface as annotations on the Files Changed tab.
- `.gitignore` and the workflow cache `path:`: use
`docs/.style/styles/*` plus a `!docs/.style/styles/Coder` negation so
adding a package does not require parallel edits.
- `.markdownlint-cli2.jsonc`: ignore the synced styles so `make
lint/markdown` does not lint upstream READMEs.

Scaffold prose under `docs/.style/` and
`.claude/docs/DOCS_STYLE_GUIDE.md` / `AGENTS.md` come from the merged
DOCS-180; this PR no longer touches them. Net diff against `main` is the
8 Vale-wiring files only.

## Severity policy (v1)

Rule severity reflects two things together: the rule's false-positive
rate against real Coder docs and the gravity of the rule. Low FPs plus
high gravity argues for `error`; lower gravity or more judgment calls
argue for `warning` or `suggestion`. v1 lands most rules at `warning`
and the wordiness rules at `suggestion`.

A rule promotes to `error` only when (a) its false-positive rate against
real content is effectively zero and (b) the existing-content violation
count for that rule is also zero. Vale exits non-zero only on
error-level alerts regardless of `MinAlertLevel`; the Makefile and CI
invoke Vale with `--no-exit` so the baseline error count from
un-overridden Google rules does not fail the build while real failures
(bad config, missing files) still propagate.

## CI integration

Vale runs as steps appended to the required `lint-docs` job in
`ci.yaml`, gated on changed Markdown:

1. **`Detect changed Markdown`** (`tj-actions/changed-files`) scopes to
changed `**.md`; the prose step re-filters to `docs/` (the `docs/**.md`
glob silently skips dot-prefixed dirs and would miss
`docs/.style/style-guide.md`).
2. **`Restore Vale styles`** (`actions/cache/restore`), keyed off
`hashFiles('.vale.ini', 'mise.toml', 'docs/.style/styles/Coder/**')`.
mise manages the Vale binary, so only the synced styles are cached.
3. **`Prepare Vale styles`** runs `make docs/.style/.vale-synced` (`mise
exec ... vale sync`).
4. **`Vale prose lint`** filters the changed set to `docs/` paths still
present on disk, then runs `mise exec ... vale --no-exit --output=line`,
emitting inline annotations via the problem matcher.
5. **`Save Vale styles`** writes the cache, gated to `refs/heads/main`
only so PR runs cannot poison the cache other branches restore from (the
zizmor `cache-poisoning` concern).

**Every Vale step is `continue-on-error: true`.** This is a deliberate
change from the original standalone-workflow design: now that Vale lives
inside the *required* `lint-docs` job, a transient `vale sync` network
failure (or first-use `mise` install blip) would otherwise block merges.
`continue-on-error` keeps Vale advisory, so only the markdownlint /
table-formatter checks above (`pnpm check-docs`) remain merge-blocking.
`vale --no-exit` additionally keeps the baseline error count from
un-overridden Google rules from failing the step.

## Verification

- `actionlint` clean on `ci.yaml` (local + `make
lint/actions/actionlint`); `zizmor --persona regular` reports no
findings.
- `make lint/prose` on the full `docs/` corpus: ~406 errors, ~5,346
warnings, ~7,928 suggestions across 461 files, exit 0 (`--no-exit`),
Vale `3.7.1` installed by mise.
- Net diff vs `main` is the 8 Vale-wiring files only; the `docs/.style/`
scaffold already matches `main`.

<details>
<summary>Implementation plan and decision log</summary>

### Why this rule set

The Vale evaluation against the full docs corpus (measured 2026-05-18)
produced ~43,940 raw violations across six candidate base styles. The
selection here drops Microsoft and RedHat (overlap with Google, and
RedHat's Spacing rule hammers technical IDs), and proselint (Annotations
rule treats `> [!NOTE]` admonitions as TODO markers).

Within the kept styles:

- **Google** is the base. Disables: `EmDash` (conflicts with `make
lint/emdash`), `Latin` (i.e./e.g. are fine for our audience), `Spacing`
(4,500 errors on `codersdk.SomeType` patterns in the auto-generated API
reference). Softened: `Parens` to `suggestion`, `WordList` to `warning`.
- **write-good** is the base, with `Passive` and `E-Prime` off.
`TooWordy` and `ThereIs` are suggestions; `Weasel` is a warning.
- **alex** is cherry-picked (not in `BasedOnStyles`): `Ablist`,
`Condescending`, `LGBTQ`, `ProfanityLikely`, `Race`, `Suicide` at
warning. The `ProfanityMaybe`/`ProfanityUnlikely` rules trip on
`execute`, `kill`, `failed`, and `attack`, which read as technical
vocabulary in our context.
- **Coder** is in `BasedOnStyles` but the directory is empty in v1.
Rules land through the per-rule tickets in the [Docs style
guide](https://linear.app/codercom/project/docs-style-guide-7828445b9afc)
project.

### Why `mise exec` instead of a download block

Vale is pinned in `mise.toml` like `actionlint` and `zizmor`, so
invoking it via `mise exec "aqua:errata-ai/vale" -- vale ...` makes the
pin the single source of truth and lets mise handle the OS/arch-specific
download. This replaced an earlier ~30-line `curl`/`tar` block whose
GNU-only `grep -oP ...\K` version extraction returned empty on macOS BSD
grep. Note: the bare `vale` short name in `mise exec` ignores the pin
and resolves to the latest release, so the full aqua key is required.

### Why `vale sync` instead of vendoring

The three style packages weigh ~272 KB combined, so vendoring is cheap.
But Vale's ecosystem treats `Packages = ` + `vale sync` as canonical,
the upstream LICENSE files are not in the package tarballs (would need
to be added manually), and the CI cache makes the sync nearly free after
the first run. Sticking with the canonical pattern keeps the repo lean
and the upgrade path obvious.

### Why `lint/prose` is not in `lint:` or `lint-light:`

Vale on the full docs corpus takes ~20s on cold caches. Forcing every
pre-commit through that would be aggressive for a feature that ships as
warnings. `make lint/typos` follows the same pattern (it is in
`lint-light` but not `lint`; CI invokes it directly). v1 keeps Vale
opt-in locally and CI-only by default; promote to `lint:` once the rule
set stabilizes.

### Exit-code handling

Two mechanisms combine, and the choice changed when the step moved into
the required `lint-docs` job:

- `vale --no-exit` suppresses Vale's non-zero exit on alerts, so the
baseline error-level violations from un-overridden Google rules do not
fail the step while the cleanup PRs land. Real failures (config invalid,
file missing) still exit non-zero.
- `continue-on-error: true` on every Vale step. Because the steps now
run inside the *required* `lint-docs` job, a `vale sync`
download/network blip must not block merges. The original (standalone,
non-required) design rejected `continue-on-error` for showing a
misleading yellow badge; in a required job that tradeoff flips, and
advisory-yellow is strictly preferable to merge-blocking-red on an
infrastructure flake. `|| true` in the Makefile was also rejected: it
swallows missing-config failures indiscriminately.

### Pre-mortem

- **Generated docs noise**: `docs/reference/` is dominated by
auto-generated content (clidocgen, apidocgen, auditdocgen,
metricsdocgen). The architectural decision is to fix the generators, not
exclude paths in Vale. Google.Spacing is the only rule silenced
specifically to defer the generator fix; everything else surfaces as
warnings.
- **First-run cost**: `mise` installs the pinned Vale (a single small
binary) and `vale sync` pulls the style packages on a cold run. The
Actions cache keyed off `hashFiles('.vale.ini', 'mise.toml',
'docs/.style/styles/Coder/**')` makes subsequent runs near-instant; the
`Coder/**` hash is defense-in-depth against
[actions/toolkit#713](https://github.com/actions/toolkit/issues/713) so
a future cache release that regresses path-negation cannot serve a stale
`Coder/` from cache.
- **Required-job blast radius**: moving Vale into the required
`lint-docs` job means any Vale step failure would gate merges. Mitigated
by `continue-on-error` on all Vale steps plus a clean skip when no
changed `docs/` Markdown remains on disk, so only `pnpm check-docs`
stays blocking.
- **Cross-platform install**: handled by mise (aqua backend) rather than
a hand-rolled `uname`/arch map, which removes the macOS BSD-grep break
the review flagged.
- **Deleted files**: `all_changed_files` is ACMRD and lists paths a PR
removes; the prose step filters to files still present on disk so Vale
does not error on a missing file.
- **Local-vs-CI parity**: CI lints changed files only; local `make
lint/prose` lints the full tree. This mirrors `make lint/markdown` (full
tree) vs the changed-files CI step. Acceptable for v1.

</details>

---

*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*
2026-06-22 14:14:31 -04:00
Cian Johnston fc83d77189 ci: correct location of redirects.json in weekly-docs workflow (#26542) 2026-06-19 12:03:55 +01:00
Ben Potter 787392ef16 ci: add docs-path redirect audit to weekly-docs workflow (#26472)
Closes
[DOCS-257](https://linear.app/codercom/issue/DOCS-257/b-vitest-validate-docs-literals-against-docsmanifestjson)

Extends `weekly-docs` with a new `audit-docs-paths` job that
cross-references TS/TSX `docs()` calls against
`coder.com/redirects.json` and fails when any path resolves via a
redirect (i.e. is stale). Also fixes two bugs in the existing
`check-docs` job:

- **Scheduled runs were a no-op** — `github-pr-review` reporter silently
exits 0 without a PR context. Now uses `local` reporter on schedule so
broken links actually fail the job.
- **Slack notification was broken** — payload used `"msg"` (invalid)
instead of `"text"` (the standard Slack webhook field).

Sample Slack output:

![slack-notification-sample](https://i.imgur.com/N6E7d9s.png)

Safe to merge in any order relative to #25740 — the audit job checks for
the script and skips gracefully if not yet available.

---

> Generated by [Coder Agents](https://coder.com) on behalf of @bpmct.
2026-06-17 11:59:35 -05:00
Nick Vigilante 182bdc871a docs: scaffold docs/.style for the prose style guide (#25466)
Adds a private contributor-tooling directory at `docs/.style/` that will
host the canonical prose style guide and the custom Vale rules used to
enforce it. The directory's contents do not deploy to `coder.com/docs`.

This PR is the scaffold only. The Vale configuration, the rule set, and
the per-rule style-guide sections all land in follow-up PRs.

## What changes

- New `docs/.style/` directory with:
  - `README.md` explaining the convention
  - `style-guide.md` as a table-of-contents scaffold
- `styles/Coder/README.md` placeholder so Git tracks the empty Vale
rules dir
- `.github/workflows/deploy-docs.yaml`: skip the workflow on
`.style`-only pushes, and exclude `.style` paths from the
surgical-reindex git diff on mixed commits. Defense-in-depth on top of
the manifest-driven coder.com routing.
- `.github/.linkspector.yml`: add `docs/.style` to `excludedDirs`
- `AGENTS.md` and `.claude/docs/DOCS_STYLE_GUIDE.md`: cross-link to the
new style guide for agents

## Verification

- `make pre-commit-light` clean (`fmt/markdown`, `lint/markdown`,
`lint/typos`, `lint/emdash`, `lint/actions/actionlint`,
`lint/shellcheck`).
- `markdown-table-formatter --check` and `markdownlint-cli2` both
process the new files (existing globs are `find docs -name '*.md'`).
- `actionlint` clean on the modified workflow.
- coder.com exclusion works because route discovery and Algolia indexing
are manifest-driven; this directory is not in `docs/manifest.json`. The
workflow changes are defense in depth.

<details>
<summary>Implementation plan and decision log</summary>

### Decisions

- **Location**: `docs/.style/` (leading dot, mirrors `.github/`,
`.vscode/`, `.claude/`). Vale's `StylesPath` will be
`docs/.style/styles/`; `.vale.ini` lands at repo root in a follow-up.
- **Existing public page `docs/about/contributing/documentation.md`**:
untouched in this PR. Nick's separate information-architecture rework
will redirect it to GitHub at the right time.
- **Placeholder for empty `styles/Coder/`**: real `README.md`, not
`.gitkeep`. Discoverable on GitHub, lints with the existing tooling,
lists the planned starter rules.
- **CONTRIBUTING.md**: not touched. It's a 2-line redirect to
`coder.com/docs/CONTRIBUTING`; bloating it would defeat the redirect.
- **`.claude/docs/DOCS_STYLE_GUIDE.md`**: kept as the structure/research
companion. A blockquote at the top points at the new canonical prose
guide.

### coder.com exclusion mechanism (verified by inspection)

Direct inspection of `coder/coder.com`:

- Route discovery in
[`src/utils/docs/docs.ts`](https://github.com/coder/coder.com/blob/master/src/utils/docs/docs.ts)
iterates `routes` from `docs/manifest.json`. Files not in the manifest
never become routes.
- The Algolia surgical indexer at
[`src/utils/algoliaDocs/surgical.ts`](https://github.com/coder/coder.com/blob/master/src/utils/algoliaDocs/surgical.ts)
explicitly skips paths not in the manifest, incrementing `pathsSkipped`.

Net result: not adding anything from `docs/.style/` to `manifest.json`
is the only thing that has to be true for the exclusion to work. The
`deploy-docs.yaml` tweaks are defense in depth.

### deploy-docs.yaml changes (pre-mortem)

1. Trigger path negation `!docs/.style/**` skips the workflow on
`.style`-only pushes. GitHub Actions only suppresses when every changed
file matches a negation, so mixed commits still trigger.
2. The git-diff pathspec `:(exclude)docs/.style/**` drops `.style` paths
from the surgical-reindex payload on mixed commits.

Risks considered:

- **Test contract**: `.github/workflows/test-deploy-docs-diff.sh` only
exercises the downstream awk parser, not the git-diff invocation. The
exclusion happens at git-diff time; the parser sees the same
`<status>\0<path>\0` format. No test change needed.
- **First push to a brand-new branch**: the workflow falls back to
whole-branch reindex when `BEFORE_SHA` is all zeros. Whole-branch
reindex re-extracts records from the manifest, which still excludes
`.style` files because they are not in the manifest.
- **Workflow-dispatch**: takes the whole-branch path; same reasoning.
Safe.

### Why a real README in `styles/Coder/` instead of `.gitkeep`

It explains intent, lists the upcoming rules, and lints with the
existing tooling. The cost is one extra Markdown file; the upside is
that a contributor browsing GitHub sees the plan without clicking
around.

</details>

---

*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*


Linear: DOCS-180
2026-06-17 13:19:37 +00:00
McKayla はな 3e68dd304a ci: set up pixel (#26324)
IT'S FINALLY HERE
2026-06-16 14:43:54 -06:00
Spike Curtis 792afc0842 ci: capture PostgreSQL logs in the gen job (#26340)
Adds a `test-postgres-docker-logs` Make target that dumps the test
PostgreSQL container's logs via `docker logs`. The container already
logs every statement to stderr (`log_statement=all`, no
`logging_collector`), so Docker captures them and no volume mounting or
reconfiguration is needed.

The CI `gen` job now starts the container with `make
test-postgres-docker` before `make gen`, collects the logs at the end
(always, even on failure), and uploads them as the `gen-postgres-logs`
artifact to help debug generation issues that depend on the database.

Refs: https://github.com/coder/internal/issues/1568

<sub>Opened by Coder Agents on behalf of @spikecurtis.</sub>
2026-06-15 16:23:36 -04:00
Nick Vigilante fb24110933 feat(.github/workflows): trigger docs reindex on release.published (DOCS-327) (#26070)
Closes
[DOCS-327](https://linear.app/codercom/issue/DOCS-327/trigger-docs-reindex-on-codercoder-releasepublished).

## What

Add `release: { types: [published] }` to
`.github/workflows/deploy-docs.yaml` so that publishing a stable
`vX.Y.Z` GitHub Release on this repo auto-dispatches the docs-sync
handler against the corresponding `release/X.Y` branch. The existing
`push` and `workflow_dispatch` triggers are unchanged.

The `Compute action and ref` step gains a release-event branch that:

- Skips prereleases (`github.event.release.prerelease == true`) with a
workflow notice.
- Matches the tag against `^v([0-9]+)\.([0-9]+)\.[0-9]+$` and translates
`v2.35.0` to `release/2.35`.
- Falls through with a notice and `exit 0` for any tag that doesn't
match the plain semver shape (`v2.35`, `v2.35.0-rc.1`, etc.).

Downstream validation, HMAC body construction, and the POST step are
unchanged. The POST step gains an `if: steps.input.outputs.action != ''`
guard so the two `exit 0` paths skip the POST instead of sending empty
`action`/`ref` to the production handler.

A new `.github/workflows/test-deploy-docs-release.sh` exercises the
release-event bash against the 11 event scenarios in the table below
plus 3 regex boundary cases, mirroring the existing
`test-deploy-docs-diff.sh` pattern.

## Why

Today, every mainline rollover requires a human to dispatch this
workflow manually with `action=index, ref=release/X.Y`. We just hit this
rotation friction on
[DOCS-324](https://linear.app/codercom/issue/DOCS-324/rotate-algolia-indexer-allowlist-for-v234-launch-add-release234-drop)
(v2.34 launch) and the resulting empty-search-results incident on
`/docs/@v2.34.x/...`. `release.published` is the right cue: it fires
exactly when a version becomes user-visible, not when its release branch
is cut weeks earlier with possibly-incomplete docs.

## Coupling (important)

This change is **intentionally inert until coder.com's
`INDEXED_REFS_BY_CORPUS` allowlist becomes self-rotating** (filed under
[DOCS-210](https://linear.app/codercom/issue/DOCS-210/automated-docs-index-lifecycle-management)).
Until that lands, the handler still rejects new minors with `{action:
"skipped", reason: "...not in INDEXED_REFS_BY_CORPUS"}` and this
workflow logs the skip. Pre-wiring lets both halves land roughly in
parallel so the next release cut after both ship is automatic.

Reviewers: feel free to merge this independently. There is no downside
to the wiring being live before the allowlist half ships; worst case,
every release-publish event creates a no-op workflow run.

## Behavior trace (the cases the bash handles)

<details>
<summary>11 event scenarios I walked through by hand</summary>

| Event | Tag | prerelease | Result |
|---|---|---|---|
| push to main | n/a | n/a | `index`, `ref=main` (existing) |
| push to release/2.34 | n/a | n/a | `index`, `ref=release/2.34`
(existing) |
| workflow_dispatch index release/2.34 | n/a | n/a | `index`,
`ref=release/2.34` (existing) |
| workflow_dispatch delete release/2.31 | n/a | n/a | `delete`,
`ref=release/2.31` (existing) |
| release.published | `v2.35.0` | `false` | `index`, `ref=release/2.35`
(new) |
| release.published | `v2.35.0-rc.1` | `true` | notice + `exit 0` (new)
|
| release.published | `v2.35.0-rc.1` | `false` | notice + `exit 0`,
regex miss (new) |
| release.published | `v2.35` | `false` | notice + `exit 0`, regex miss
(new) |
| release.published | `release-2.35` | `false` | notice + `exit 0`,
regex miss (new) |
| release.published | `v0.0.0` | `false` | `index`, `ref=release/0.0`
then handler rejects via allowlist (defense in depth) |
| release.published | `` (empty) | unset | notice with `<unknown>` +
`exit 0` |

</details>

## Safety

- The handler's allowlist gate still applies; this PR can only cause
`{action: "skipped"}` responses until DOCS-210's allowlist-derivation
lands. No risk of indexing an unintended ref.
- The workflow's existing input validation (`case "$REF" in
main|release/*)`) rejects any translation output that isn't
`release/<int>.<int>`. Defense in depth in case the regex ever loosens
by accident.
-
[DOCS-121](https://linear.app/codercom/issue/DOCS-121/post-mortem-docs-search-outage-2026-05-12-pr-25049-merge-wiped-docs)
self-trigger risk is not present here: the new trigger is
`release.published`, not push-on-paths. Workflow file edits cannot
induce a release event.
- `concurrency: { group: deploy-docs-${{ github.ref }} }` already
exists. Release events have `github.ref=refs/tags/vX.Y.Z`, distinct from
push events on the same release branch. A theoretical race resolves
through the handler's atomic deleteBy+saveObjects.
- The POST step now has an `if:` guard that skips downstream calls when
the Compute step exits early without writing outputs. Closes the
empty-env-var failure mode that coder-agents-review CRF-1 flagged.

## Verification

- `actionlint .github/workflows/deploy-docs.yaml` clean.
- `make pre-commit-light` clean: `fmt/shfmt`, `fmt/markdown`,
`lint/actions/actionlint`, `lint/shellcheck`, `lint/markdown`,
`lint/emdash`, `lint/typos`, etc.
- `.github/workflows/test-deploy-docs-release.sh`: 14 cases pass (11
scenario table + 3 regex boundary cases).
- Bash logic hand-traced through 11 event scenarios (table above).

## Out of scope

- Build-time allowlist derivation in coder.com (DOCS-210a, will be
filed/PR'd as a sibling change).
- Webhook-driven cleanup of aged-out refs
([DOCS-210](https://linear.app/codercom/issue/DOCS-210) parent).
- code-server release lifecycle (different repo, code-server's docs
corpus stays at `main`).

---

_Coder Agents on behalf of @nickvigilante._
2026-06-12 17:54:32 -04:00
Danny Kopping fda6fc9345 ci(.github/workflows): label cherry-pick PRs with the target release (#25887)
## Summary

Adds a `cherry-pick/v<version>` label to the cherry-pick PRs that the
`Cherry-pick to release` workflow creates automatically, so cherry-picks
for a specific release can be filtered and identified easily (for
example
`cherry-pick/v2.31`).

## Changes

- Compute `CHERRY_PICK_LABEL="cherry-pick/v${VERSION}"` from the
resolved
  release branch.
- Create the label on demand with `gh label create --force` so the
workflow stays idempotent across re-runs and concurrent runs, and works
  even when the label does not exist yet.
- Apply the label at PR creation via `gh pr create --label`.
- Grant `issues: write` permission, required to create the label.
- Document the new label convention in the workflow header.

## Notes

The version is derived from the existing release-branch resolution
(`release/2.X` -> `2.X`), so no new configuration is required. The label
name uses a `v` prefix to match the requested `cherry-pick/vX.YZ`
format.

<details>
<summary>Implementation context</summary>

The label is created before the existing-PR idempotency check and
applied
in the same `gh pr create` call already used for assignees/reviewers, so
it
fits the workflow's existing conventions (branch, title, body) without
changing control flow.

</details>

---

*This PR was created by Coder Agents on behalf of @dannykopping.*
2026-06-11 20:13:03 +02:00
Garrett Delfosse b95697a370 ci: rewrite release workflow to be fully GitHub Actions-driven (#25162)
Replace the local interactive release CLI and legacy shell scripts with
a non-interactive Go tool (`scripts/release-action/`) and a rewritten
`release.yaml` workflow. Release managers trigger releases from the
GitHub Actions UI by selecting a branch, picking a release type (`rc`,
`release`, or `create-release-branch`), and optionally providing a
commit SHA.

The Go tool has four subcommands: `calculate-version` (computes next
version from git state), `generate-notes` (release notes from commit log
and PR metadata), `publish` (creates GitHub release with checksums), and
the workflow handles tag creation, branch creation, building, and
downstream publishing.

`scripts/version.sh` fallback now uses `git describe` (nearest ancestor
tag) instead of global latest so dev builds on release branches show the
correct version series.
2026-06-04 14:38:48 -04:00
Atif Ali 6ef687cdfb chore: remove Nix dev image from dogfood template and pipeline (#26022) 2026-06-03 16:49:27 +05:00
Dean Sheather 6c230d6e0f chore(.github): remove fly.io workspace-proxy deployment (#25126)
Removes the fly.io-based workspace-proxy deployment from CI. The dogfood
workspace proxies in Paris (`cdg`), Sydney (`syd`), and Johannesburg
(`jnb`) are no longer deployed via fly.io, and the São Paulo proxy
session-token secret was already unreferenced in `deploy.yaml`.

## Changes

- Deleted `.github/fly-wsproxies/{paris,sydney,jnb}-coder.toml`.
- Removed the `deploy-wsproxies` job from
`.github/workflows/deploy.yaml`,
along with its `workflow_call.secrets` block declaring the five `FLY_*`
  inputs.
- Removed the matching `secrets:` pass-through from the `deploy` job in
  `.github/workflows/ci.yaml`.

The Kubernetes/EKS dogfood deploy job and `should_deploy.sh` logic are
unchanged.

## Repository secrets that can now be deleted

Once this lands, the following GitHub Actions repository secrets are no
longer referenced anywhere in this repo and are safe to remove:

- `FLY_API_TOKEN`
- `FLY_PARIS_CODER_PROXY_SESSION_TOKEN`
- `FLY_SYDNEY_CODER_PROXY_SESSION_TOKEN`
- `FLY_JNB_CODER_PROXY_SESSION_TOKEN`
- `FLY_SAO_PAULO_CODER_PROXY_SESSION_TOKEN` (was already passed through
  but unused inside `deploy.yaml`)

Worth double-checking they aren't referenced by any other repos / org
workflows before deleting from the org/repo settings.

## Out of scope (intentionally left alone)

- `site/static/icon/fly.io.svg` — region icon, used at runtime for any
  user-deployed workspace proxy that picks the fly.io icon.
- `docs/install/other/index.md` — unofficial "Run Coder on Fly.io"
  community install entry, unrelated to our CI.
- `site/src/testHelpers/entities.ts` `*.fly.dev.coder.com` strings — UI
  test fixtures.

## Validation

- `python3 -c "yaml.safe_load(...)"` on both edited workflows.
- `make pre-commit` ran via the git hook on commit (actionlint,
shellcheck,
  typos, helm, markdown, etc. all green).
- Repo-wide grep confirms no remaining `FLY_`, `flyctl`, `fly.toml`, or
  `fly-wsproxies` references in `.github/` or `scripts/`.
2026-06-03 21:22:41 +10:00
Ethan 5088b5fa5f ci: extend flake-go bump test-count to 35 (#25981)
Two changes to make the `flake-go` workflow produce better signal when
something hangs or flakes at low rates.

**Job timeout (20m → 25m).** The Go-level `-timeout 20m` baked into
`make test` (`Makefile:1428`) currently raced the runner's 20m
hard-kill, so a hanging test got SIGTERM'd by Actions instead of
SIGQUIT'd by Go, and we never got the goroutine dump. Bumping the
workflow job to 25m mirrors the layering already used by `test-go-pg` in
`ci.yaml:409` and gives Go's timeout the 5m head start it expects.

**Test count (25 → 35).** Catches lower-frequency flakes that 25
attempts miss too often. For a 5% per-run flake, detection probability
goes from ~72% at n=25 to ~83% at n=35; for 1–2% flakes the lift is
larger. The longest successful flake-go run to date was 11m49s at n=25,
so n=35 should peak around ~16–17m and stay well inside the new 25m
budget.
2026-06-03 00:20:42 +10:00
Thomas KosiewskiandClaude Opus 4.8 f6a4ed309f ci: fix Windows runner PATH casing for mise, not in cli (#25972)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 10:46:40 +00:00
Thomas KosiewskiandClaude Opus 4.8 550aa6d6a2 ci: install gotestsum in flake check workflow (#25934)
The Flake Check workflow runs `make test` through the `test-go-pg`
action, which invokes `gotestsum`, but the workflow never installs it.
The mise refactor (#25727) deleted the `setup-go` action that previously
installed `gotestsum` implicitly, and added explicit `mise install ...
go:gotest.tools/gotestsum` steps to every other Go test job. The flake
check's `Install Go mise tools` step only listed `whichtests`, so the
check fails with `gotestsum: command not found` whenever it selects
changed tests to run.

Add `go:gotest.tools/gotestsum` to the flake check's install step,
matching the other `test-go-pg` jobs in `ci.yaml` and
`nightly-gauntlet.yaml`.

Refs #25727

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 12:11:00 +10:00
Mathias Fredriksson ed4311b2cb ci: add Git usr/bin to PATH on Windows (#25939)
## Summary

Fixes all 9 Windows CI test failures caused by the mise CI refactor
(`fe257666d7`, PR #25727).

### Root cause

`jdx/mise-action` exports `Path` (Windows convention) via `GITHUB_ENV`.
Bash on Windows maintains its own `PATH`. When Go's `os.Environ()`
returns both, `cmd.exe` subprocesses non-deterministically pick the
MSYS-translated `PATH` (forward slashes), causing Windows executables
(`printf`, `powershell.exe`, `cmd.exe`) to be unresolvable.

These failures only appeared on `main` (where `-count=1` forces real
test execution) and were masked on PRs by Go test cache.

### Fixes applied

**CI (`setup-mise` action)**:
- Write both `Path` and `PATH` to `GITHUB_ENV` with Git usr/bin
prepended

**Code (`cli/root.go`)**:
- Add `appendAndDedupEnv` helper that deduplicates case-insensitive env
vars on Windows, preferring native Windows paths (backslashes) over MSYS
paths

**Code (`cli/configssh_windows.go`)**:
- Use absolute paths for `powershell.exe` and `cmd.exe` in the SSH
config `Match exec` escape function, avoiding PATH resolution entirely

**Tests**:
- Switch `--header-command` tests from `printf` to `echo` (cmd.exe
builtin) for reliable cross-platform execution
- Add env dedup in `Test_sshConfigMatchExecEscape` for subprocess PATH
consistency

Fixes coder/internal#1556, coder/internal#1558, coder/internal#1559

> 🤖 Generated by Coder agent, will be reviewed by @mafredri. 🏂🏻
2026-06-02 11:51:16 +10:00
Thomas Kosiewski fe257666d7 ci: refactor CI to use mise for shared tool setup (#25727) 2026-06-01 15:55:19 +02:00
Ethan 76d3181aba ci(.github/workflows): bump action-linkspector to v1.5.2 (#25882)
The `check-docs` job has been failing on every PR touching `docs/**`
since 2026-05-29. `umbrelladocs/action-linkspector` runs linkspector
under puppeteer, which expects an exact Chrome build (e.g.
`148.0.7778.97`) in `/home/runner/.cache/puppeteer`. When that build
isn't present on the hosted runner, linkspector crashes with `Could not
find Chrome` and reviewdog then fails parsing the empty rdjson output
with `proto: syntax error`.

The pinned `v1.4.1` of the action was installing linkspector `0.4.7`,
whose puppeteer requires `148.0.7778.97`; that build is no longer in the
runner cache. Upstream `v1.5.2` upgrades linkspector to `0.5.3` and adds
Chromium fallback logic, but on `ubuntu-22.04` x86_64 none of its new
code paths fire (the AppArmor branch is gated on `lsb_release -rs ==
"24.04"`, the system-Chromium branch on aarch64 or missing 24.04
sysctl), so the bump alone leaves the same Chrome error in place.

This PR:

- Bumps the action to `v1.5.2` (linkspector `0.5.3`).
- Sets `PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome` on the action
step. The hosted `ubuntu-22.04` image ships Google Chrome at that path.
`v1.5.2`'s `script.sh` short-circuits Chromium setup when this env is
set, so puppeteer skips the cache lookup and uses the runner binary
directly.

End-to-end verified by temporarily perturbing `docs/**` on this branch
so the workflow's `pull_request` trigger would fire:
https://github.com/coder/coder/actions/runs/26732938434. `check-docs`
ran linkspector against `docs/**` for ~2m30s and exited 0, with no
`Could not find Chrome` or reviewdog parse errors in the log. That
perturbation has been removed from the branch.

Refs UmbrellaDocs/action-linkspector#62,
UmbrellaDocs/action-linkspector#61
2026-06-01 13:42:37 +10:00
Nick Vigilante e32fdc813b ci: rerun docs preview job on subsequent pushes (#25456)
Fixes DOCS-174: the docs-preview workflow only fired on `pull_request:
opened`. Subsequent pushes left the preview comment stale.

## Changes

- Add `synchronize` and `reopened` to trigger types so subsequent pushes
retrigger the workflow.
- Add a workflow-level `concurrency` group keyed by PR number with
`cancel-in-progress: true` so rapid successive pushes don't race the
comment-upsert lookup.
- Replace always-create comment logic with an upsert: find the existing
comment containing `<!-- docs-preview -->` and PATCH it; fall through to
create only when none exists or the PATCH itself fails (comment was
deleted between find and update).
- Filter the upsert lookup to comments authored by `github-actions[bot]`
so a human comment containing the marker is never silently overwritten.
- Decouple the `gh api` lookup from the `head -n 1` pipe so API failures
(network, auth, rate-limit) propagate immediately instead of being
swallowed by `|| true`.
- Delete the stale preview comment when a `synchronize` push drops all
Markdown changes (e.g. a follow-up push that removes the file an earlier
push had previewed but still touches `docs/`). The previous preview
comment would otherwise point at a deleted page.
- Extract the marker and the comment-selector jq into a single
`DOCS_PREVIEW_MARKER` variable and a `list_docs_preview_comments` shell
function so the stale-cleanup and upsert branches share one source of
truth.

## Out of scope

Vercel ISR cache invalidation for feature branch previews requires a
coder.com change (the `algolia-docs-sync` endpoint only accepts `main`
and `release/*` refs). Tracked separately in DOCS-174 out-of-scope
notes.

Pulls that fully revert their `docs/` changes in a follow-up push won't
fire this workflow at all (GitHub's `paths` filter requires a path match
in the diff), so a stale preview comment can survive on that specific
edge. Removing the `paths` filter to handle it would run the workflow on
every PR push, which is disproportionate. Acknowledged in
[CRF-12](https://github.com/coder/coder/pull/25456#discussion_r3313738550).

<details>
<summary>Implementation notes</summary>

**Marker and selector deduplication**: The marker string and jq selector
previously appeared at three sites (comment body, stale-cleanup API
call, upsert API call). They're now consolidated into
`DOCS_PREVIEW_MARKER` plus a `list_docs_preview_comments` shell function
so a future marker change updates one place.

**Comment body construction**: The double-quoted multi-line string form
with escaped backticks (`` \` ``) for the inline-code spans is
shellcheck-clean. An earlier draft used `printf -v comment_body` with a
single-quoted format string containing backticks, which triggered
SC2016; the printf-three-pieces workaround that replaced it has since
been simplified to the direct double-quoted form.

**Upsert logic**: `gh api --paginate` fetches all PR comments, jq
filters to `github-actions[bot]`-authored comments containing the
marker, and the workflow PATCHes the first match. If the PATCH fails
(404 because the comment was deleted between find and update), the
script falls through to `gh pr comment` to create a new one. Self-heals
on the next push if both paths somehow fail.

**Stale-cleanup logic**: Same selector as upsert, but in the early-exit
branch when no Markdown files exist in this push. `DELETE` failures are
logged and execution continues (the next push will re-attempt or post a
fresh comment), so a transient API failure won't fail the CI job.

</details>

> Generated by Coder Agents on behalf of @nickvigilante
2026-05-28 10:03:21 -04:00
Danny KoppingandClaude Opus 4.7 f91390b2c8 ci: don't fail job if commenting on locked PR (#25765)
The final step of `.github/workflows/cherry-pick.yaml` comments on the
original PR with a link to the cherry-pick PR. If the original PR is
locked, `gh pr comment` fails and the whole job exits with status 1,
even though the backport branch and PR were created successfully.

See
https://github.com/coder/coder/actions/runs/26559681779/job/78239379200
for an example.

Make the comment non-fatal: log a warning and continue.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 13:26:04 +02:00
Mathias Fredriksson 673709bd34 ci(.github/workflows/doc-check): update agents-chat-action to v0.3.0 (#25784) 2026-05-28 11:16:34 +00:00
Ethan ca7f07142e ci: add Go test flake detector workflow (#25667)
Adds a `flake-go` workflow that hunts for ordering-dependent and racy Go
tests on pull requests. The workflow runs only on PRs (cancelling
earlier runs on new commits) and skips test execution when no Go test
files changed.

A single `flake_go` job uses
[coder/whichtests](https://github.com/coder/whichtests) with
`--coalesce` to compute the directly-modified `Test*` functions from the
PR diff and emit them as one target row. The same job then runs those
selected tests on a deliberately resource-constrained 4-vCPU runner with
4x parallelism oversubscription, `-count=25`, and `-shuffle=on` to
amplify contention and surface flakes.

Pinned at
[coder/whichtests@ec33bab](https://github.com/coder/whichtests/commit/ec33bab1ec04cd86beb7a61a069db4463dba63f5).

Reuses the `test-go-pg` composite (with its new `run-regex`,
`test-shuffle`, and `gotestsum-json-file` inputs) and the
`go-test-failure-report` composite, both introduced on the base branch
(#25670), so this workflow shares one implementation of the gotestsum +
failure-report path with the existing CI jobs.

`Makefile` adds `TEST_SHUFFLE` support and single-quotes `RUN` so
whichtests' regex survives shell parsing.

Stacked on top of #25670.

Demo @
https://github.com/coder/coder/actions/runs/26494322649/job/78018779381?pr=25667

Closes CODAGT-381
2026-05-28 12:35:37 +10:00
blinkagent[bot]andblink-so[bot] 1bfc1ce2c4 chore: update terraform to v1.15.5 (#25746)
Bumps bundled Terraform from `1.15.2` to `1.15.5` across all pinned
locations:

- `.github/actions/setup-tf/action.yaml`
- `scripts/Dockerfile.base`
- `install.sh`
- `flake.nix` (+ updated SRI hash for the linux_amd64 zip)
- `mise.toml`
- `mise.lock` (+ updated per-platform SHA256 checksums)
- `provisioner/terraform/testdata/version.txt`
-
`provisioner/terraform/testdata/resources/ai-tasks-disabled/ai-tasks-disabled.tfplan.json`

## Why

Terraform 1.15.5 is built with Go 1.25.10, while the 1.15.2 we currently
ship was built with Go 1.25.8. The newer Go runtime addresses recent
stdlib CVEs flagged by security scanners.

Releases included: 1.15.3 (provider install crash fix, nested-module
stack migration fix), 1.15.4 (Linux s390x builds, symlinked provider dir
fix), 1.15.5.

Release notes:
https://github.com/hashicorp/terraform/releases/tag/v1.15.5

## Cherry-pick

#25747 mirrors this PR against `release/2.34`.

Created on behalf of @Shelnutt2

Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
2026-05-27 16:46:25 -04:00
Garrett Delfosse 5991a2c8b0 ci: trigger CI on release branch creation (#25744)
GitHub Actions does not reliably trigger the push-based CI workflow when
a new branch is created at a commit that already has a workflow run from
another branch (e.g. `main`). This meant cutting a release branch
produced no CI run on it, so `should_deploy.sh` never got to approve the
deploy from the release branch.

Adds the `create` event trigger to `ci.yaml` with a condition on the
`changes` job to only proceed for release branch creations. All other
jobs depend on `changes`, so non-release branch creations are a no-op.

> Generated with [Coder Agents](https://coder.com/agents) by @f0ssel
2026-05-27 14:46:18 -04:00
Mathias Fredriksson 2730a87975 ci(.github/workflows/doc-check): update agents-chat-action to v0.2.0 (#25731) 2026-05-27 17:51:18 +03:00
Ethan f422ac89cc ci: extract go-test-failure-report composite action (#25670)
The Go test jobs in `ci.yaml` each had ~30 lines of inline shell that
wrapped `gotestsum` with a PATH shim to capture JSON, then ran
`gotestsummary` and `upload-artifact` to publish a failure report. Three
jobs carried three near-identical copies.

This change replaces the three inline blocks with a single composite
action at `.github/actions/go-test-failure-report/` that runs the same
`gotestsummary` invocation, writes the same markdown to
`GITHUB_STEP_SUMMARY`, and uploads the same NDJSON artifact. The PATH
shim is gone; gotestsum's native `GOTESTSUM_JSONFILE` env variable is
used instead, plumbed through the `test-go-pg` composite.

`test-go-pg` gains three optional inputs:

- `gotestsum-json-file` — explicit JSON file path (or `default` for
`${RUNNER_TEMP}/go-test.json`)
- `run-regex` — passed to `go test -run`
- `test-shuffle` — passed to `go test -shuffle`

All three have safe defaults so existing callers are unaffected.

No observable change in CI behavior: the three existing test-go-pg jobs
continue to emit the same JSON, render the same failure summary, and
upload the same artifact.

Stacked under #25667, which uses the new composite and inputs to power a
new flake-detector workflow.
2026-05-28 00:16:46 +10:00
Ethan e99f7171e4 ci: require docs lint when docs change (#25608)
Move docs linting into the required CI umbrella and reuse the existing
`changes` job so docs lint runs when docs or CI files change, plus on
`main` as a backstop.

This is motivated by the docs lint failures on #25601. That PR touched
`.claude/docs/TESTING.md`; the standalone `Docs CI` workflow picked it
up because `docs-ci.yaml` used broad `**.md` matching, but local `pnpm
lint-docs` and `make lint` did not catch the same file because they only
scanned `docs/**` plus root `*.md`. The first failed Docs CI run
reported markdownlint errors in `.claude/docs/TESTING.md` (`MD040` and
`MD031`), and the next run reported a markdown table formatter failure
in the same file.

That mismatch is why this PR exists: prevent unrelated PRs from being
surprised by stale `.claude/docs/**` lint drift only after they happen
to touch one of those files. The local docs scripts now include
`.claude/docs/**`, and the old standalone `Docs CI` workflow is removed
so we do not maintain separate path-filter logic outside the required CI
workflow.

> Generated by mux, but reviewed by a human
2026-05-27 12:30:05 +10:00
Thomas KosiewskiandClaude Opus 4.7 51836e681e refactor: build dogfood image as base + mise oci layers (#25448)
Splits the dogfood image into two artifacts:

- `ghcr.io/coder/oss-dogfood-base:<distro>-<base-sha>`: Ubuntu base with
apt packages, chrome, rustup, brew, gh, and the mise binary. The
base-sha is a cache key over `Dockerfile.base` and `files/`, so commits
that don't touch those inputs reuse the previous build.
- `codercom/oss-dogfood:<final-sha>-<distro>` and rolling tags
(`:22.04`, `:26.04`, `:latest`, `:<branch>`): produced by `mise oci
build` on top of the base, with one content-addressed OCI layer per mise
tool. The rolling tag scheme is unchanged, so the workspace template
doesn't need updating.

Single-tool version bumps now invalidate only that tool's OCI layer, so
workspaces re-pull just what changed instead of the entire 5-6 GB image
on every recreate.

Also:

- Drops the build-time `pnpm dlx playwright@1.47.0 install --with-deps
chromium` step (~400 MB) and the equivalent `playwright-driver.browsers`
install from `flake.nix`. `@playwright/mcp` (used by the claude-code and
codex MCP servers in `dogfood/coder/main.tf`) does NOT auto-install
browsers, so the existing `install-deps` `coder_script` now runs two
installs on workspace start: `pnpm exec playwright install chromium` for
the site's pinned `@playwright/test`, and `npx
--package=@playwright/mcp@latest playwright-core install --no-shell
chromium` so the MCP servers find their matching browser revision.
Browser revisions coexist under
`~/.cache/ms-playwright/chromium-<rev>/`, which lives on the home volume
so both downloads happen once per workspace recreate and persist across
restarts. Net effect: same MCP behavior as before, +~1-2 min on first
workspace start. Nix devshell users running site e2e tests locally now
need `pnpm exec playwright install` once (instead of getting browsers
via nixpkgs).
- Bumps the pinned mise binary to v2026.5.12 (matching main after
#25521) and adds top-level `min_version = "2026.5.12"` to `mise.toml` so
every consumer (devs, CI, the embedded mise inside the dogfood image,
mise oci builds) fails fast on an older mise.
- Adds bison, flex, libicu-dev, libreadline-dev, uuid-dev, and
zlib1g-dev to both Ubuntu base images for source-build use cases (e.g.,
building Postgres from source).
- Replaces skopeo with crane as the registry client `mise oci push`
shells out to: crane is added to `mise.toml`, the workflow drops its
`apt-get install skopeo` and forces `--tool crane`, and the local
wrapper image stops bundling skopeo. One source of truth for tool
versions, no apt drift, smaller wrapper image, and workspace users get a
registry client on PATH for free via mise oci's tool layers.
- Removes `nix.hash`/`mise.hash` and their Makefile rules. The registry
digest already captures every effective change since CI rebuilds when
any baked-in input moves; the per-file `filesha1()` entries in
`pull_triggers` are redundant.

Supersedes #25400 (the `mise.hash` pull trigger landed there in
`2b612abe7b`; this PR removes it as part of the broader simplification).

> [!NOTE]
> `mise oci build` is experimental and requires `MISE_EXPERIMENTAL=1`
(set at job level in the workflow). The local-only
`scripts/dogfood/mise-oci-wrapper.sh` builds a tiny
`coderdev/mise-oci-wrapper:<version>` Debian image with curl-installed
mise on first invocation (cached by version tag thereafter); we don't
reuse `jdxcode/mise:latest` because that tag lags upstream GitHub
releases by days and would defeat the `min_version` enforcement above.

> [!NOTE]
> `compute-base-sha.sh` and `compute-final-sha.sh` are cache keys, not
strict content addresses: the base Dockerfile still pulls dynamic
resources at build time (gh/buildx `releases/latest`, chrome
`stable_current_amd64.deb`, apt mirror state). Two runs with identical
checked-in files can produce slightly different bytes, which is
acceptable here because the cache-hit savings on irrelevant commits
outweigh that drift.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:52:21 +02:00
Cian Johnston 579daaff70 feat: add GitLab support to coderd/externalauth/gitprovider
Fixes CODAGT-146

Add GitLab support to the gitprovider package for gitsync/chatd PR
diff flows. This is a squashed stack of 3 PRs:

#25651 - refactor(coderd/externalauth): prepare gitprovider for multi-provider support
- Change gitprovider.New to return (Provider, error)
- Extract shared helpers (parseRetryAfter, checkRateLimitError,
  countDiffLines, escapePathPreserveSlashes) from github.go
- Update all callers (db2sdk, exp_chats, gitsync) for new signature
- Add error logging for provider construction failures
- Thread context through provider resolution

#25652 - feat(coderd/externalauth/gitprovider): add GitLab provider
- Implement full Provider interface: FetchPullRequestStatus,
  FetchPullRequestDiff, FetchBranchDiff, ResolveBranchPullRequest
- Handle nested groups, forks, and self-hosted instances
- Rate limit detection on both library and raw HTTP paths
- URL parsing/building with NormalizePullRequestURL support
- Unit tests covering error paths, URL parsing, state mapping
- Document GitLab configuration and known limitations

#25653 - test(coderd/externalauth/gitprovider): add GitLab VCR integration tests
- FetchPullRequestStatus: 4 fixtures (open, conflicts, merged, closed)
- FetchPullRequestDiff: 4 fixtures
- FetchBranchDiff: 3 fixtures (open, deleted, fork)
- ResolveBranchPullRequest: 3 fixtures
- go-vcr cassettes with sanitized GitLab API responses
2026-05-25 17:41:02 +01:00
Cian Johnston 0a45f96d30 ci: validate dogfood image tooling by running gen, fmt, lint, build (#25475)
Adds a `test_image` job that runs `make gen`, `make fmt`, `make lint`, and `make build` inside the
newly built image via `docker run`. This helps detect breaking changes before merge. 

> [!NOTE]
> Generated with [Coder Agents](https://coder.com/agents)
2026-05-25 17:02:13 +01:00
Mathias Fredriksson 471249f3e2 ci: migrate doc-check workflow to coder/agents-chat-action (#25178)
Replace the inline `curl` + `jq` block in
`.github/workflows/doc-check.yaml` with a single `uses:
coder/agents-chat-action` step.

Closes CODAGT-375
2026-05-22 19:09:36 +03:00
Nick Vigilante 5840ac5f6e ci(.github/workflows/docs-ci.yaml): scope changed-files per tool (#25317)
## Problem

`Docs CI` fails on PRs that only touch binary assets under `docs/`.
Example: [#25314](https://github.com/coder/coder/pull/25314), which
swaps a single PNG and produces thousands of `MD010/no-hard-tabs`,
`MD049/emphasis-style`, and `MD018/no-missing-space-atx` errors at
columns like 16,285 of the image.

## Root cause

The single `tj-actions/changed-files` step was doing two jobs at once:
detecting which Markdown files changed (for `lint` and `fmt`), and
gating whether the workflow had anything to do at all. Its `files`
filter matched `docs/**` in addition to `**.md`, so any non-Markdown
file under `docs/` (PNG, GIF, JPG, MP4, SVG) ended up in
`all_changed_files` and was passed straight to `markdownlint-cli2`,
which opened the file and parsed the binary bytes as Markdown.

`markdownlint-cli2`'s own `ignores` setting is a discovery-time filter
and does not gate files passed explicitly on the command line, so the
filtering has to happen in the caller.

## Fix

Adopt a per-tool convention: each downstream tool gets its own
`changed-files` step scoped to the files that tool can process. For now
that is a single `changed-md` step matching `**.md`, consumed by `lint`
and `fmt`. A future tool (e.g. an image linter, video size check, or
link checker) can be added purely additively, by appending another
`changed-*` step and a step that consumes its output, without changing
the existing filters.

The workflow-level `on.push.paths` / `on.pull_request.paths` triggers
stay broad (`docs/**`, `**.md`) so the workflow still runs on
screenshot-only PRs; the per-tool filters decide which individual steps
execute. On a screenshot-only PR the existing `if:
steps.changed-md.outputs.any_changed == 'true'` guard skips `lint` and
`fmt` cleanly.

## Verification

- `actionlint .github/workflows/docs-ci.yaml` passes.
- Reproduced the original failure locally: `pnpm exec markdownlint-cli2
docs/images/install/install_from_deployment.png` produces the same flood
of violations seen in the failing CI run on #25314.
- First revision of this PR (workflow with `**.md`-only filter, single
`changed-files` step) was green on `Docs CI`; the current revision is
structurally equivalent for the existing tools and just renames the step
id and adds the per-tool comment.

<details>
<summary>Decision log</summary>

- Considered adding `ignores` to `.markdownlint-cli2.jsonc` to skip
non-Markdown files. Rejected: `markdownlint-cli2` treats `ignores` as a
discovery-time glob filter and still lints files passed explicitly on
the command line, so it would not have fixed the failure.
- Considered narrowing the existing single `changed-files` step's
`files` filter to `**.md` only. Rejected as the final shape: it solves
the immediate bug but conflates "which Markdown files changed" with
"should the workflow run at all", so adding a second tool with a
different file set later (e.g. an image linter) would require contorting
or duplicating that step.
- Chose the per-tool-filter shape so adding a future tool is additive:
one new `changed-*` step plus one new step that consumes its output,
with no edits to existing steps.

</details>

## Disclosure

Opened on behalf of @nickvigilante by Coder Agents.
2026-05-15 10:15:25 -04:00
Nick Vigilante aa87d55a6d ci(.github/workflows): audit workflow self-references in paths (#25288)
Three workflows besides `deploy-docs.yaml`
([DOCS-124](https://linear.app/codercom/issue/DOCS-124),
[#25285](https://github.com/coder/coder/pull/25285)) self-reference in
their `paths:` triggers: `docker-base.yaml`, `docs-ci.yaml`,
`dogfood.yaml`. This was flagged during review of #25285
([DEREM-1](https://github.com/coder/coder/pull/25285#discussion_r3234975475))
as a bug class worth treating uniformly. This PR is the audit.

Each self-reference is either justified inline or removed:

* **`docker-base.yaml`** keeps the self-reference. It's PR-only and
gated by `push: ${{ github.event_name != 'pull_request' }}` on the
`depot/build-push-action`, so PRs build the base image without
publishing.
* **`docs-ci.yaml`** drops the self-reference. The `lint` and `fmt`
steps gate on `tj-actions/changed-files` matching `docs/**` or `**.md`,
so a workflow-only run no-ops. `actionlint` and `make lint/actions`
catch YAML problems before merge regardless.
* **`dogfood.yaml`** keeps the self-reference. PR runs build images
without pushing and run `terraform init` + `validate` only; pushes to
main retag rolling tags on `codercom/oss-dogfood`,
`oss-dogfood-vscode-coder`, and `oss-dogfood-nix`, plus `terraform
apply` against dev.coder.com which produces new `coderd_template`
versions with unchanged content. Idempotent and bounded.

Refs DOCS-121, DOCS-129.

<details>
<summary>Decision table</summary>

| Workflow | Self-ref location | Effect on workflow-only edit | Decision
|
|---|---|---|---|
| `deploy-docs.yaml` | push + workflow_dispatch | Destructive (DOCS-121)
| Removed in [#25285](https://github.com/coder/coder/pull/25285) |
| `docker-base.yaml` | PR-only | Build base image, never push | Keep
with inline comment |
| `docs-ci.yaml` | push + PR | Empty run; lint/fmt skipped by `if:` |
Remove (wasted runner minutes) |
| `dogfood.yaml` | push + PR | PR: build without push, terraform
validate. Main: retag rolling tags, terraform apply, new cosmetic
template versions | Keep with inline comment |

</details>

---
_Coder Agents on behalf of @nickvigilante._
2026-05-15 08:49:17 -04:00
Nick Vigilante 81b6132e02 fix(.github/workflows/deploy-docs.yaml): drop self-trigger from paths (#25285)
Edits to `.github/workflows/deploy-docs.yaml` previously self-triggered
the workflow on push to `main` and `release/*` because the file was
listed in its own `paths:`. On 2026-05-12, this caused merge of #25049
to fire a production reindex with no `docs/**` changes, which entered
the empty-`paths_json` whole-branch path in the Algolia handler and
wiped the `docs` index (see DOCS-121).

This change removes `.github/workflows/deploy-docs.yaml` from `paths:`
so the workflow only runs against real docs content. Reindexes from a
workflow edit alone now require `workflow_dispatch`, which already
accepts a `ref` input and an `action` choice of `index` or `delete`. The
other safety net (a workflow-level `paths_json=[]` guard in
`algolia-and-isr`) is tracked separately in DOCS-122.

Refs DOCS-121, DOCS-122, DOCS-124.

---
_Coder Agents on behalf of @nickvigilante._
2026-05-15 08:48:48 -04:00
Thomas KosiewskiandClaude Opus 4.7 5f9b3220b5 chore: install dogfood image tooling via mise.toml (#25282)
This PR replaces the hand-rolled `curl | tar | go install | cargo
install` chains in the dogfood Ubuntu 22.04 and 26.04 Dockerfiles with a
single `mise install` driven by a new repo-root `mise.toml`.

The previous Dockerfiles installed ~25 CLIs across three multi-stage
builds with versions hardcoded inline. Version bumps were scattered
across the Dockerfiles, the root `mise.toml` (added in #24618 but
otherwise unused at runtime), and CI's setup actions; build-time network
failures came from a dozen distinct endpoints; and `mise` itself sat in
the image with no manifest to install from.

The new flow:

- The repo's `mise.toml` is the single source of truth for image tool
versions. The Dockerfiles `COPY` it to `/etc/mise/config.toml` and run a
single `mise install` as the `coder` user.
- Tools are installed into `/opt/mise/data` rather than the default
`/home/coder/.local/share/mise`, so they live in the image (not on the
persistent home volume) and reach every workspace on recreate.
- Build context moves to the repo root so the Dockerfile can `COPY
mise.toml`; an allowlist `.dockerignore` keeps the transferred context
to ~24 kB.
- Optional `--secret id=github_token` plumbing through the Makefile and
`.github/workflows/dogfood.yaml` lifts aqua's GitHub API quota from
60/hr unauthenticated to 1000/hr with `secrets.GITHUB_TOKEN`.
- `MISE_TRUSTED_CONFIG_PATHS=/home/coder:/etc/mise` is set as an ENV so
users who clone the coder repo into their workspace home aren't prompted
to `mise trust`.

Net diff for the two Ubuntu Dockerfiles: -399 / +244 lines (~200 lines
shorter each). The `FROM rust-utils`, `FROM go`, and `FROM proto`
multi-stage builds are gone; so are the NVM/Node block, the bulk
binary-install block (golangci-lint, helm, kubectx, syft, cosign, bun),
the gh `.deb`/lazygit/doctl tarball installs, the gofmt
`update-alternatives` line, and the `yq`→`yq4` rename
(`scripts/lib.sh:267-275` already auto-detects either name).

Both images were built and smoke-tested with Apple's `container` CLI on
macOS — every migrated tool resolves to the expected pinned version
including outside the cloned coder repo (e.g. `gh` from `/home/coder`,
matching the workspace startup script in `dogfood/coder/main.tf`),
`sqlc` runs (proving `CGO_ENABLED=1` was honoured at install), `yq
--version` reports v4 for `scripts/lib.sh`'s detection, and `gofmt`
resolves via the mise shim.

Follow-ups (out of scope here):

- Commit a multi-platform `mise.lock` so `gh = "latest"` and the other
floating versions resolve deterministically across rebuilds and dev
machines.
- Migrate CI's `setup-go` / `setup-node` actions to consume `mise.toml`
so image and CI versions stop being able to drift.

---------

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:36:22 +02:00
Spike Curtis 132fa87bf3 fix: only embed Azure roots on darwin (#25312)
Partially reverts #25136 for non-darwin platforms.

In general we want to avoid pinning trust roots to embedded Certs, since that limits operational flexibility. If Azure changes CAs, operators should, at most, be able to update the OS trust store to keep Coder working correctly. Embedding roots means we need to upgrade the Coder binary.

Since Coder Server on macOS is not really supported for production use, embedding only in that case to ease development and testing is OK.
2026-05-14 11:45:21 -04:00
Thomas Kosiewski f71bccf53f ci(.github/actions/setup-node): verify active Node version (#25143)
Updates the shared setup-node composite action to current Node 24 based
releases of `pnpm/action-setup` and `actions/setup-node`. This avoids
the deprecated Node 20 action runtime seen in CODAGT-178 while keeping
the third-party actions pinned by SHA.

Adds an explicit post-setup check that fails inside Setup Node when
`node --version` is not `v22.19.0`, so self-hosted runner/toolcache
mismatches are surfaced before `pnpm install` reports a dependency
engine error.

Closes https://github.com/coder/internal/issues/1457

Generated by Coder Agents.
2026-05-14 12:07:09 +02:00
Ethan 8955599bd0 fix: bump sqlc fork to v1.31.1 merge, strip pg_dump meta-commands (#25105)
Closes https://github.com/coder/internal/issues/965

Recent `pg_dump` patch releases (13.22+ / 14.19+ / 15.14+ / 16.10+ /
17.6+) emit `\restrict` / `\unrestrict` psql meta-commands at the head
and tail of schema dumps. These broke both `sqlc` and our
`scripts/migrate-test` schema-equality check. PR #19696 worked around it
by pinning `pg_dump` to a Docker image.

This change unpins the workaround now that `sqlc` handles the
meta-commands:

* Bumps the coder/sqlc fork pin to [`337309b` on
coder/sqlc:main](https://github.com/coder/sqlc/commit/337309bfb9524f38466a5090e310040fc7af0203),
the merge of upstream v1.31.1 (coder/sqlc#6). v1.31.1 includes
[sqlc-dev/sqlc#4390](https://github.com/sqlc-dev/sqlc/pull/4390), the
upstream `\restrict` / `\unrestrict` parser fix. Updated in three places
that pin the fork SHA: `flake.nix` (`sqlc-custom`),
`.github/actions/setup-sqlc/action.yaml`, and the
`dogfood/coder/ubuntu-{22,26}.04` Dockerfiles. The flake's `sha256` /
`vendorHash` are reset to `pkgs.lib.fakeSha256`; Nix will surface the
real hashes on first build, per the existing comment block.
* Reverts #19696's Docker pin in `coderd/database/dbtestutil/db.go`.
Local `pg_dump` (13+) and the `postgres:13` Docker fallback both work
again.
* Strips `\restrict` / `\unrestrict` lines in `normalizeDump` so
`scripts/migrate-test`'s schema comparison is stable across `pg_dump`
versions (the token in those lines is randomized per run).
`TestNormalizeDumpStripsRestrict` locks the behavior in.
* Regenerates with v1.31.1, picking up the version stamp and one
upstream correctness fix in `DeleteLicense`
([sqlc-dev/sqlc#4383](https://github.com/sqlc-dev/sqlc/pull/4383): don't
shadow the input parameter when scanning a single-column return).
2026-05-13 18:55:24 +10:00
Nick Vigilante 36d52ba504 feat(.github/workflows): trigger Algolia, ISR, and Vercel deploy on docs/** changes (#25049)
Folds the Algolia/ISR sync trigger and surgical-reindex path computation
into the existing `deploy-docs.yaml` workflow so a single `docs/**` push
fires every update path the docs site needs.

One preflight job feeds two parallel sibling jobs:

- **`changes`** (preflight): diffs `github.event.before` against
`github.sha` to compute `manifest_changed` and `paths_json` (a JSON
array of `{path, status}` objects derived from `git diff --name-status
-z`, capped at 50 entries). The mapping is `A → added`, `M/T →
modified`, `D → deleted`, `R<n> → renamed` (indexed by the new path).
Falls back to whole-branch (emits `paths_json: "[]"`) on
`workflow_dispatch`, the first push to a new branch, fetch failure,
manifest changes (route restructuring would orphan records), or >50
markdown files.
- **`algolia-and-isr`** (always, parallel with `vercel-rebuild`):
HMAC-signed POST to `coder.com/api/algolia-docs-sync` with the
`paths_json` array as part of the body. Refreshes the Algolia `docs`
slice for the `(corpus, ref)` pair and ISR-revalidates every navigable
route the handler touched. Markdown-only edits surface in seconds with
no full rebuild. The step summary line `Mode: \`surgical\` (N path(s))`
lets operators verify which path ran without scrolling through the curl
output.
- **`vercel-rebuild`** (parallel with `algolia-and-isr`, only when
`docs/manifest.json` changed): fires the existing Vercel deploy hook for
a full build. Manifest changes can register or remove routes that
Next.js's `getStaticPaths` only re-evaluates on a full build, so
ISR-per-existing-path is not enough.

Trigger expanded from "main + manifest.json" to "main and `release/*` +
any `docs/**`" so release-branch docs edits also flow through the same
pipeline. The Vercel rebuild path stays gated on manifest changes
regardless of branch.

The pure shell + curl + openssl + jq + awk pipeline is preserved
verbatim. Zero Algolia or Node dependencies in CI.

## Why one workflow instead of two

The original split (a standalone Algolia workflow + the existing
`deploy-docs.yaml`) would have run twice per manifest push, with two
parallel concurrency groups, two GitHub Actions step summaries, and two
ways to forget to add a secret. Folding into one file makes the trigger
story symmetrical: "docs change → all docs surfaces refresh," with the
rebuild path being a strict superset of the ISR path, and the surgical
path strictly cheaper than whole-branch when computable.

## Pre-merge testing

The companion handler PR (coder/coder.com#741) supports an
`ALGOLIA_DOCS_INDEX` env-var override, scoped to `docs_smoke` on the
Vercel preview deploy, so this workflow can be exercised end-to-end
against a disposable index without touching production records. The
smoke harness at `~/audit/smoke/run.sh` (workspace-only) signs and posts
the same body shape this workflow does, so it covers the same crypto
path. To exercise the workflow itself, push a docs-only commit to a
throwaway branch and watch the step summary; the `algolia-and-isr` job
will print the resolved mode.

## Prerequisites before this can do anything useful

1. `secrets.ALGOLIA_DOCS_SYNC_SECRET` must be added as an Actions secret
on this repo. The same value goes on `coder.com`'s Vercel env. The
workflow logs a clear error and aborts with no network call if the
secret is missing.
2. The handler at coder/coder.com#741 must be merged and deployed.
Without it, the POST will 404.
3. `secrets.DEPLOY_DOCS_VERCEL_WEBHOOK` is already in place from the
existing `deploy-docs.yaml`; this PR does not change its usage.

## Demo, validation, and design

- Front-end-only fixes (modal layout, scroll-shadow, rank-order
preservation): coder/coder.com#749 ships these against production today,
independent of this PR.
- Companion handler PR on `coder.com`: coder/coder.com#741. Includes the
surgical-mode plumbing this workflow's `paths_json` output drives.
- Full design lives in the workspace at
`~/plans/algolia-search-revamp.md`. Key sections:
  - §6.0–6.2: why the indexer lives in `coder.com`, not here.
  - §6.7: per-version add/remove mechanics.
  - §6.8: ISR revalidate rationale and same-time refresh.
- §6.9: surgical per-page reindex (workflow + handler + planning rules).

---

This PR was generated by Coder Agents.
2026-05-12 14:18:31 -04:00