-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathtend.example.yaml
More file actions
499 lines (476 loc) · 20.6 KB
/
Copy pathtend.example.yaml
File metadata and controls
499 lines (476 loc) · 20.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# # Configuration
#
# Place at `.config/tend.yaml`. Regenerate workflows after editing:
#
# uvx tend@latest init
#
# Only `bot_name` is required. Everything else has sensible defaults.
#
# Parsed under YAML 1.2 (ruamel.yaml), so `on`, `yes`, `no`, `off` stay
# strings — only `true`/`false` are booleans.
bot_name: my-project-bot
# ## Harness
#
# Which agent runtime to use. Defaults to "claude" (the official `claude`
# binary run headless behind a credential-injecting proxy). "codex" runs
# OpenAI Codex via `codex exec`.
#
# harness: codex
# ## Model
#
# Model to use for all workflows. Any string the selected harness's CLI accepts
# — an alias that follows the provider's promotions (`opus`, `sonnet`,
# `haiku`), or an exact id that stays put across them (`claude-opus-5`,
# `gpt-6-sol`). Tend passes the value straight to `--model`, so a name the
# CLI does not know fails the job with that CLI's error. The defaults are
# `opus` for Claude and `gpt-6-sol` for Codex.
#
# model: opus
# Codex installs write their model explicitly. During a Tend upgrade, move an
# explicit pin to a newer model only within the same capability and price tier.
# ## Codex subscription auth
#
# auth_refresh: true (default) generates the serialized weekly refresher.
# auth_refresh: false uses credentials published by your own refresher.
#
# codex:
# auth_refresh: false
# ## Effort
#
# Reasoning effort for the selected model. Codex accepts low, medium, high,
# and xhigh; Claude also accepts max. A model that does not read the level
# ignores it. Empty (default) leaves the choice to the harness CLI.
#
# effort: medium
# ## Additional harness arguments
#
# Exact arguments passed to `claude` or `codex exec`, before Tend's managed
# arguments. Each list item is one argv element, so values containing spaces
# stay intact. The selected CLI validates incompatible or repeated options.
#
# args: [--max-turns, "40"] # Claude example
#
# `harness`, `model`, `effort`, and `args` can instead live under one
# `workflows.<name>` entry. That workflow then overrides the top-level value;
# `args: []` clears inherited arguments.
# ## Merge mode
#
# `restricted` is the default: the bot can push branches and open PRs, but only an
# admin can update the default branch. `yolo` lets the bot merge PRs into the
# default branch while still blocking direct pushes. In yolo mode, `tend check`
# verifies a final CODEOWNERS block for `.github/**`, `.config/tend.yaml`, the
# CODEOWNERS files themselves, and agent instructions at any depth (`CLAUDE.md`,
# `CLAUDE.local.md`, `AGENTS.md`, `AGENTS.override.md`, `.claude/`, `.agents/`).
# GitHub requires a fresh approval from any listed user before those changes
# can merge. `tend check --fix` creates the block with the current `gh` user;
# maintainers can add more user names to each line. The bot must not be listed.
#
# merge: yolo
# ## Protected branches
#
# The default branch is always protected. Additional long-lived branches
# (e.g. release or staging branches) can be listed here. They remain
# admin-only under either merge mode.
#
# protected_branches: ["release", "staging"]
# ## Secrets
#
# Required secrets, by harness, under these exact names — stored in the
# repo's `tend` GitHub Environment (install-tend creates it; a workflow the
# bot pushes to a branch cannot read them there):
#
# | Harness | Required |
# |------------|---------------------------------------------------------------------------|
# | `claude` | `TEND_BOT_TOKEN` + one of `CLAUDE_CODE_OAUTH_TOKEN` or `ANTHROPIC_API_KEY` |
# | `codex` | `TEND_BOT_TOKEN` + `OPENAI_API_KEY`, or subscription auth below |
#
# `TEND_BOT_TOKEN` is the bot account's PAT (scopes below).
# `CLAUDE_CODE_OAUTH_TOKEN` is from `claude setup-token` (PKCE).
# `ANTHROPIC_API_KEY` is a console.anthropic.com API key.
# `OPENAI_API_KEY` is a standard OpenAI API key. Experimental Plus/Pro auth
# gives agent jobs access-only `CODEX_AUTH_JSON`. By default, this
# repository's weekly refresher needs its own full `CODEX_REFRESH_AUTH_JSON`
# and `CODEX_REFRESH_PAT` in `tend-codex-refresh` (scoped to this repository,
# Environments: write). Only access-only `CODEX_AUTH_JSON` belongs in `tend`.
# Never copy a full Codex login between repositories: their refresh jobs would
# invalidate one another's tokens. To manage refresh externally, set
# codex.auth_refresh: false and run `tend init`. Store only
# CODEX_AUTH_JSON in tend; remove unused secrets from tend-codex-refresh.
# The external refresher must publish a replacement before the token expires.
# This relies on internal Codex auth behavior; see README.md and the
# install-tend security model.
#
# The Claude action prefers the subscription token
# (`CLAUDE_CODE_OAUTH_TOKEN`) over the API key when both are set.
# `TEND_MEMORY_GIST_ID` is required only when the experimental `memory_gist`
# setting is true. It keeps the secret Gist's unlisted URL out of a public
# config file.
#
# Classic PAT scopes: `repo`, `workflow`, `notifications`, `write:discussion`,
# `gist`, `user`. `workflow` is required to push commits that modify
# `.github/workflows/` files. `gist` covers two uses of secret gists owned by
# the bot: internal skills store structured evidence in them, and the
# experimental `memory_gist` setting persists Claude Code's auto memory across
# runs. `user` lets `install-tend` set the bot's profile bio so contributors
# can see the authorization stance.
#
# Fine-grained PAT permissions: `contents:write`, `pull-requests:write`,
# `issues:write`, `actions:write`, `workflows:write`, `discussions:write`,
# `notifications:write`, `gists:write`, plus the account-level `Profile:
# read and write` permission (for the bio). `notifications:write` is
# required so the action can mark threads read after handling an event.
#
# `tend check` flags repo-level secrets not in an explicit allowlist — each
# entry is a deliberate acceptance that any workflow the repo runs can read
# that token. The operational secret names are refused here (a repo-level
# copy of one would reopen what the environment gate closes); a build token
# that belongs at repo level must be listed:
#
# secrets:
# allowed: ["CODECOV_TOKEN", "DEPLOY_KEY"]
# ## Setup steps
#
# Build tools, caches, and environment variables that run before the agent in
# every workflow. A `setup:` entry mirrors a GitHub Actions step: give it exactly
# one of `uses` or `run`, plus any of these optional fields:
#
# name, id, if, with, env, shell, working-directory,
# continue-on-error, timeout-minutes
#
# For multi-step setup, add multiple entries to the `setup:` list — they
# render in declared order. For anything more elaborate, move the YAML into a
# local composite action (`.github/actions/tend-setup/action.yaml`) and reference
# it from one entry with `uses`. In `yolo`, runner-side setup is a trial:
# ordinary code the bot merged to the default branch can run here before the
# sandbox starts, with the job's credentials available to runner processes.
#
# Setup runs against the default branch, or in `tend-review` the PR's base
# branch, checked out before the PR's own tree lands. What it builds reaches
# the agent (see "The agent's sandbox" below); what the PR itself changes,
# such as a new dependency in its lockfile, the agent installs in the session.
#
# `tend-notifications` decides up front whether the agent needs to boot and
# injects an `if:` guard on every setup step so a run with no unread
# notifications or possible PR conflicts does no setup either. A user-supplied
# `if:` narrows that guard; it cannot run setup after the pre-check declined the
# job. Write it as a plain expression or one whole `${{ ... }}`; mixing the two
# is rejected at `init`.
#
# The two forms below show the config and the YAML they produce inside the
# `steps:` block. `tend-notifications` adds its no-work guard to each step.
#
# `uses`:
#
# setup:
# - uses: astral-sh/setup-uv@v10.2.0
#
# # renders as (in tend-notifications):
# # - uses: astral-sh/setup-uv@v10.2.0
# # if: steps.check.outputs.count != '0' || steps.check.outputs.conflict_count != '0' || github.event_name == 'workflow_dispatch'
# #
# # in other workflows:
# # - uses: astral-sh/setup-uv@v10.2.0
#
# `uses` with action inputs and step-level env:
#
# setup:
# - uses: actions/setup-node@v7
# with:
# node-version-file: .node-version
#
# # renders as:
# # - uses: actions/setup-node@v7
# # with:
# # node-version-file: .node-version
#
# `run` with shell and working directory:
#
# setup:
# - run: cargo build --release
# shell: bash
# working-directory: ./crates/core
# env:
# RUSTFLAGS: -D warnings
#
# # renders as:
# # - run: cargo build --release
# # shell: bash
# # working-directory: ./crates/core
# # env:
# # RUSTFLAGS: -D warnings
# ## The agent's sandbox (both harnesses)
#
# `setup:` runs as the runner user in both modes, before the composite action.
# In yolo, bot-merged code can run here before the sandbox starts.
# The agent then runs as a separate, non-sudo user inside a hardened systemd
# unit, in the job's own checkout and home, with the job's PATH and
# environment. It sees them through a copy-on-write view: what `setup:`
# prepared (a toolchain, a warm cache) is there at its own path, the agent
# writes wherever the runner itself could — a path only root can write is not
# one of them — and nothing it writes reaches the runner or a later step. The
# sandbox's `/tmp` is its own RAM-backed tmpfs; write anything bulky under
# `$TMPDIR`.
#
# The view covers the runner account's home, so the job's checkout has to sit
# inside it, as it does on GitHub-hosted runners. A self-hosted runner whose
# work folder is outside that home is not supported yet: the job fails in its
# "Set up credential-isolation sandbox" step, naming both paths. Configuring
# the runner's work folder under its home avoids it.
#
# So anything `setup:` leaves readable in the runner's home or checkout (a
# registry login, a cloud credential file), and anything exported to
# `$GITHUB_ENV` or set in job-level `env:`, is readable by the agent, and so by
# anyone who can open a pull request. Log in only in steps tend does not run.
# Tend masks the Actions runner's own files and GitHub's file-command directory.
#
# The sandbox refuses `AF_UNIX` sockets: creating one returns `EPERM`,
# and the boundary asserts that at startup, so it is not something a config
# key relaxes. Anonymous pipes and `socketpair()` are unaffected; anything
# that opens a named socket is not, however closely its two ends are
# related. So a build tool that drives worker processes over one loses them
# — and .NET's "named pipes" are `AF_UNIX` sockets, so MSBuild loses its
# worker nodes silently: a multi-project `dotnet build` prints
# `Build FAILED.` with `0 Error(s)` and no diagnostic. `-m:1` keeps MSBuild
# in one process and makes real errors visible again.
#
# The sandbox also refuses new namespaces (`unshare --user` fails), so a tool
# that builds its own sandbox from one, such as bubblewrap, a rootless
# container runtime, or Chromium's sandbox, cannot start inside it.
#
# Tend appends a pinned `uv` fallback after the agent's PATH. It installs no
# other language toolchain, and puts nothing on PATH before `setup:` runs, so
# a `setup:` step that calls `uv` needs `astral-sh/setup-uv` ahead of it, as
# in any workflow. A directory a `setup:` step appends to
# `$GITHUB_PATH` is on the agent's PATH:
#
# setup:
# - run: echo "/opt/my-tool/bin" >> "$GITHUB_PATH"
#
# A variable a `setup:` step exports to `$GITHUB_ENV` is in the agent's
# environment the same way:
#
# setup:
# - run: echo "RUST_BACKTRACE=1" >> "$GITHUB_ENV"
#
# Tend's proxy routing, CA trust, and dummy credentials win over a variable of
# the same name. Every command the sandbox runs inherits that environment, and
# on `tend-review` and on a mention on a pull request those commands include
# the pull request's own code whenever the agent builds or tests that tree.
# Tend's own GitHub and model credentials never reach the sandbox — a
# runner-side proxy injects them into the requests that need them; the one
# exception is Codex under subscription auth, which receives an expiring
# access-only token. A third-party credential has no such route, so a secret
# exported for the agent is readable by anyone who can open a pull request.
#
# Give the agent the narrowest credential that does the job: that is the
# mitigation that holds everywhere, because the value sits in the agent's env
# on every workflow, and no event confines the session to text: a session on
# any event can check out a pull request or run a reproduction from an issue
# body. Withholding it from the events that carry unreviewed code closes the
# largest path. Hand the secret to the step through its `env:`, where a GitHub
# Actions expression can gate it — name the events that get it rather than the
# ones that don't, and keep the secret on the `&&` side, since an empty string
# is falsy and `<cond> && '' || secrets.X` hands over the secret whatever
# `<cond>` says. Every step after it sees the value as well, so add it last:
#
# setup:
# - run: echo "MY_TOKEN=$MY_TOKEN" >> "$GITHUB_ENV"
# env:
# MY_TOKEN: ${{ github.event_name == 'schedule' && secrets.MY_TOKEN || '' }}
#
# `$GITHUB_ENV` reads one `NAME=VALUE` per line, so a value that expands to
# more than one line sets variables of its own. A multi-line secret needs
# GitHub's `NAME<<DELIMITER` form, with a delimiter the value cannot contain.
#
# ## Memory Gist (experimental, Claude harness)
#
# `memory_gist: true` restores Claude Code's model-authored auto memory from a
# Gist before each Claude run and saves its changes afterwards. This setting is
# experimental. The default is false, and the field is rejected when no enabled
# workflow uses the Claude harness.
#
# The bot account must own a secret Gist containing `MEMORY.md`. Its description
# must be exactly `tend auto memory: OWNER/REPO`. Store the Gist ID in the
# `TEND_MEMORY_GIST_ID` secret of the repo's `tend` environment:
#
# gh secret set TEND_MEMORY_GIST_ID --repo OWNER/REPO --env tend
#
# Secret Gists are unlisted, not private, so this experiment supports public
# repositories only and memory must not contain credentials or private facts.
# Recalled notes are untrusted context: they may be stale or influenced by text
# Claude read in an earlier run. Gists are flat, so a nested memory directory
# makes that run's save fail with a warning. If a file this run changed also
# changed remotely, the run skips its entire save. GitHub's Gist API still
# cannot make the final update atomic.
#
# memory_gist: true
# ## Workflows
#
# All workflows are generated by default except ci-fix (requires
# `watched_workflows`) and externally managed Codex auth refresh. Workflows
# accept these options:
#
# - `enabled` (bool) — omit this workflow on the next regeneration
# Codex auth refresh uses `codex.auth_refresh` instead.
# - `prompt` (string) — override the default skill invocation. May span lines.
# Three workflows substitute one placeholder into it: `{pr_number}` (review),
# `{issue_number}` (triage), `{run_id}` (ci-fix). Everything else is passed to
# the agent verbatim, braces and quotes included — but `${{ ... }}` is a
# GitHub Actions expression and is evaluated, so don't write one as prose.
# `mention` composes its prompt from the triggering event and takes no
# override; setting one there is an error.
#
# Scheduled workflows also accept `cron` to override their default schedule.
# GitHub runs `schedule` triggers on a best-effort basis and drops ticks under
# load, so a cron is the requested cadence rather than a guarantee — observed
# gaps between runs are routinely longer than the interval.
#
# workflows:
# notifications:
# enabled: false
# ### review
#
# Triggers on PR open/update. Reviews for correctness, duplication, error paths.
# Monitors CI. Pushes fixes to bot-authored PRs.
#
# workflows:
# review:
# prompt: "/my-custom-review {pr_number}" # override default prompt
#
# tend-review checks out the prospective post-merge tree
# (`refs/pull/N/merge`) when available, and falls back to the PR branch head
# (`refs/pull/N/head`) when it isn't — GitHub only materializes the merge ref
# for mergeable PRs, so the fallback keeps review running on PRs with merge
# conflicts against the base. On fallback the review sees the PR branch in
# isolation rather than the post-merge tree.
# ### mention
#
# Triggers on @bot mentions and comments in issues and PRs, and on the review
# events mention-relay passes it. Responds to requests in PR and issue
# conversations.
# ### mention-relay
#
# Triggers on submitted reviews and edited inline review comments on same-repo
# PRs, and passes them to mention from a job that holds no secrets; each review
# adds this one check to the PR. Generated only while mention is enabled. With
# `enabled: false`, reviews reach the bot through the notifications poll
# instead, minutes later, as reviews on fork PRs already do.
#
# workflows:
# mention-relay:
# enabled: false
# ### triage
#
# Triggers on new issues. Classifies, checks for duplicates, reproduces bugs,
# attempts conservative fixes.
# ### ci-fix
#
# Triggers when a watched CI workflow fails or is cancelled on the default
# branch. Diagnoses the run and opens a fix PR when needed.
#
# workflows:
# ci-fix:
# watched_workflows: ["ci", "build"] # required — names of CI workflows to monitor
# branches: ["main", "v1"] # optional — defaults to default branch only
# ### nightly
#
# Daily (default 06:17 UTC). Resolves conflicts on open PRs, reviews recent
# commits, surveys ~10 files for bugs and stale docs, closes resolved issues,
# regenerates tend workflow files.
#
# workflows:
# nightly:
# cron: "0 8 * * *" # override schedule
# ### weekly
#
# Weekly (default Sunday 09:17 UTC). Reviews dependency PRs and approves safe
# patch and minor updates; merging follows the configured mode.
#
# workflows:
# weekly:
# cron: "0 10 * * 1" # override to Monday
# ### notifications
#
# Every 15 minutes. Keeps the bot watching the repository, drains unread
# notifications as a recovery queue, and repairs conflicts on bot-authored PRs.
# ### review-runs
#
# Daily (default 07:47 UTC). Reviews recent CI runs for behavioral problems
# and proposes skill/config improvements.
# ## Workflow overrides
#
# The generator owns every `tend-*.yaml` file — editing them directly loses
# changes on the next `uvx tend@latest init`. To customize the generated YAML,
# set overrides in `.config/tend.yaml`; they survive regeneration.
#
# Two scopes are supported:
#
# - `workflow_extra` — adds or replaces top-level keys (e.g. `env`, `defaults`)
# - `jobs.<name>` — adds or replaces keys inside a specific job
#
# Overrides use RFC 7396 (JSON Merge Patch): mappings deep-merge, scalars and
# lists replace, and `null` deletes the key. Unknown job names print a warning
# but don't fail. Step-level overrides aren't supported — use the `setup:`
# mechanism above to inject steps. Yolo refuses both override forms so
# credential-bearing generated jobs retain their audited shape.
#
# ### Example: skip review on labeled PRs
#
# To skip PRs carrying a `tend:dismissed` label (useful once a PR has been
# reviewed and the author doesn't want re-reviews on every push), replace the
# `review` job's `if:`. Scalars replace under JSON Merge Patch, so repeat the
# `TEND_ENABLED` pause check the generated `if:` holds — `init` refuses an
# override on an agent job that drops it:
#
# workflows:
# review:
# jobs:
# review:
# if: "vars.TEND_ENABLED != 'false' && !contains(github.event.pull_request.labels.*.name, 'tend:dismissed')"
#
# ### Example: extend permissions without losing defaults
#
# Mappings deep-merge, so adding one permission preserves the rest:
#
# workflows:
# review:
# jobs:
# review:
# permissions:
# packages: read
#
# ### Example: longer timeout on a specific job
#
# workflows:
# review:
# jobs:
# review:
# timeout-minutes: 240
#
# ### Example: target a specific job in a multi-job workflow
#
# workflows:
# mention:
# jobs:
# handle:
# timeout-minutes: 180
#
# ### Example: top-level env vars
#
# workflows:
# review:
# workflow_extra:
# env:
# MY_VAR: hello
#
# ### Example: delete a generated key with null
#
# YAML has a native `null` literal, so JSON Merge Patch's delete-key
# semantics work directly. Drop the cron schedule from nightly while
# keeping `workflow_dispatch`:
#
# workflows:
# nightly:
# workflow_extra:
# on:
# schedule: null