This document is the maintainer guide for GitHub Actions, changelog
synchronization, notifications, and deployment automation in tenzir/news.
Markdown files under workflows/ aren't necessarily documentation. In
particular, workflows/changelog-x.md is an executable input to GitHub Agentic
Workflows and must remain at its current path.
| Path | Purpose |
|---|---|
actions/sync/action.yaml |
Triggers this repository's synchronization workflow from a source repository. |
aw/actions-lock.json |
Caches immutable action pins for reproducible agentic workflow compilation. |
workflows/sync.yaml |
Serializes changelog synchronization and sends Discord notifications. |
workflows/changelog-x.md |
Defines the editable agentic workflow that drafts and processes X posts. |
workflows/changelog-x.lock.yml |
Contains the generated, executable GitHub Actions workflow. Don't edit it manually. |
workflows/changelog-x-check.yaml |
Tests changelog parsing and X automation on pull requests. |
workflows/rebuild-content.yaml |
Requests a tenzir/content rebuild after a push to main. |
scripts/ |
Contains deterministic parsing, notification, validation, and publication helpers. |
Source repositories trigger workflows/sync.yaml. The workflow clones the
source repository and synchronizes its changelog into a top-level project
directory.
The workflow runs in tenzir/news and uses concurrency: group: "sync" to
serialize updates. This avoids concurrent source repositories racing while
they pull and push the same target repository.
Each run performs these steps:
- Validate that the source exists in the
tenzirorganization. - Clone the source repository.
- Synchronize the primary changelog and configured module changelogs.
- Commit and push the result to
main. - Notify configured destinations about new entries and releases.
- Dispatch a rebuild request to
tenzir/content.
The workflow uses these organization or repository settings:
| Setting | Purpose |
|---|---|
vars.TENZIR_GITHUB_APP_ID |
Identifies the Tenzir GitHub App. |
secrets.TENZIR_GITHUB_APP_PRIVATE_KEY |
Creates installation tokens. |
Add a workflow like this to the source repository:
name: Sync to News
on:
push:
branches: [main]
paths:
- "changelog/**"
workflow_dispatch:
jobs:
sync:
name: Trigger news sync
runs-on: ubuntu-latest
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.TENZIR_GITHUB_APP_ID }}
private-key: ${{ secrets.TENZIR_GITHUB_APP_PRIVATE_KEY }}
repositories: news
- name: Trigger sync workflow
uses: tenzir/news/.github/actions/sync@main
with:
token: ${{ steps.app-token.outputs.token }}The action derives the following defaults from the source repository:
project: The repository name, such asmcpfortenzir/mcp.target: The project name and target directory intenzir/news.path:changelog.
Override the target when the synchronized directory and source repository have different names:
- uses: tenzir/news/.github/actions/sync@main
with:
token: ${{ steps.app-token.outputs.token }}
target: platformFor an initial import, set skip_notifications: true. Remove it after the
first synchronization.
For a repository with module changelogs, add each module path to the source
workflow trigger and pass the paths through extra_paths:
on:
push:
branches: [main]
paths:
- "changelog/**"
- "plugins/*/changelog/**"
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.TENZIR_GITHUB_APP_ID }}
private-key: ${{ secrets.TENZIR_GITHUB_APP_PRIVATE_KEY }}
repositories: news
- uses: tenzir/news/.github/actions/sync@main
with:
token: ${{ steps.app-token.outputs.token }}
extra_paths: "plugins/*/changelog"The extra_paths input accepts space-separated glob patterns. Each matching
directory remains under the target project. For example,
plugins/foo/changelog synchronizes to
PROJECT/plugins/foo/changelog in this repository.
You can also dispatch the workflow without the composite action:
gh workflow run sync.yaml \
--repo tenzir/news \
--field project=PROJECT \
--field path=changelogThe synchronization workflow can notify Discord and trigger the X drafting
workflow. Setting skip_notifications: true adds [skip notifications] to the
sync commit, which suppresses both channels.
Configure these optional repository secrets:
| Secret | Purpose |
|---|---|
DISCORD_CHANGELOG_WEBHOOK |
Notifies about new unreleased entries. |
DISCORD_RELEASE_WEBHOOK |
Notifies about new top-level releases. |
When a secret is absent, the workflow skips the corresponding notification. Notification failures don't fail the synchronization run.
Every push to main checks for newly added feature entries matching
PROJECT/changelog/unreleased/SLUG.md. Deterministic Python preprocessing
loads each entry through the tenzir-ship API. A
GitHub Agentic Workflow uses GPT-5.6 Sol to
draft the post or thread.
The workflow doesn't support released entries, nested changelogs, or
changes/ directories. It processes at most 25 feature entries per run and
fails before inference when a batch exceeds that limit. It serializes up to
100 pending runs in FIFO order so a burst of synchronized entries retains each
push's commit range.
The model has no credentials or write permissions. It can only request a typed
publish_x_thread safe output. After gh-aw accepts the typed request, the
main-only Python publisher reparses each entry, binds it to the triggering Git
diff, and validates the following properties before any authenticated client
is constructed or any write occurs:
- The requested entry and content hash match the triggering diff.
- Every post fits X's weighted-character limit.
- The canonical Tenzir changelog URL occurs once in the final post.
- The posts contain no mentions, em dashes, or unexpected URLs.
- The thread shape matches the changelog content.
AI threat detection is disabled for this workflow. The drafting agent is credentialless and read-only, while the publisher validates every typed request before it constructs authenticated clients. A second model pass would add AI usage and latency without guarding the X credentials or write operation.
Each post links to the project's changelog on tenzir.com, not to its
implementation pull request. The URL uses the canonical project ID from
changelog/config.yaml.
For example, an entry under skills/ links to
https://tenzir.com/changelog/tenzir-skills/. The project page remains stable
when an entry moves from the unreleased section into a versioned release.
The workflow explicitly allows tenzir.com so gh-aw's safe-output sanitizer
preserves that URL. The drafting agent has no general web tool.
Live publication uses OAuth 1.0a and retries only connection-establishment timeouts and explicit rate limits, which are known not to have created a post. Ambiguous timeouts and server errors stop immediately. A GitHub check-run ledger provides deduplication and partial-thread resume, binds progress to the exact drafted posts, and blocks another write after an ambiguous outcome.
To recover an ambiguous write, inspect @tenzir_company. Delete the post if X
created it, then manually dispatch the same entry with Retry after confirming
and removing any ambiguous X post enabled. The explicit dispatch and retry
flag provide operator confirmation before publication resumes.
Staged mode runs the model and all validation, then renders the proposed thread in the Actions step summary without creating an X post or a GitHub check run. The production workflow has staged mode disabled.
Only the publish_x job uses the social-production environment. It runs
after the secret-free typed-output gate, restricts publication to main, and
validates and publishes in one execution. The environment has no required
reviewers or wait timer, so validated feature entries publish without human
intervention. Store these secrets in that environment:
| Secret | Purpose |
|---|---|
X_API_KEY |
OAuth 1.0a consumer key for the X application. |
X_API_SECRET |
OAuth 1.0a consumer secret for the X application. |
X_ACCESS_TOKEN |
Read-and-write token for the Tenzir X account. |
X_ACCESS_TOKEN_SECRET |
Secret for the read-and-write access token. |
Run a branch preview before merging a workflow change:
- In the
tenzir/newsrepository, open Actions. - Select Draft changelog features for X.
- Click Run workflow and select the pull request branch.
- Enter an existing direct feature path, such as
tenzir/changelog/unreleased/SLUG.md. - Start the workflow.
- Download the
agentartifact and inspectagent_output.json.
The branch preview consumes Copilot credits but cannot run the main-only
publication job. A direct unreleased entry whose type isn't feature produces
a no-op before inference. An invalid entry path fails deterministic validation.
Edit workflows/changelog-x.md, then regenerate
workflows/changelog-x.lock.yml. Install the exact
gh-aw v0.82.12 release
used by the lock, then compile it. The compiler uses the immutable action SHAs
in aw/actions-lock.json:
gh extension remove aw
gh extension install github/gh-aw --pin v0.82.12
gh aw compile changelog-x --approve --validateThe workflow selects copilot/gpt-5.6-sol by its provider-scoped ID, which
resolves an exact model catalog match without a local alias. Don't replace it
with the built-in gpt-5.6 alias because that can resolve to Luna, Sol, or
Terra. Version 0.82.12 also excludes job-output credential variables from the
agent sandbox. It retains fast failure for saturated Copilot invocation caps,
safe-output client token separation, and GitHub MCP server v1.6.0. None of
these changes alters this workflow's publication graph. The agent runs without
sudo or host access, and the workflow must not opt into legacy security.
The production workflow has live publication enabled. These prerequisites must remain in place:
- Keep Allow use of Copilot CLI billed to the organization enabled under Organization Settings → Copilot → Policies → Copilot CLI.
- Keep GPT-5.6 Sol enabled in the Tenzir organization's Copilot model policy.
- Confirm that all four X secrets exist in
social-productionand represent@tenzir_companywith read-and-write access. - Keep
social-productionrestricted tomainwithout required reviewers or a wait timer. - Maintain enough X API credits and an appropriate spending limit.
- Keep
safe-outputs.stagedandGH_AW_SAFE_OUTPUTS_STAGEDset tofalseinworkflows/changelog-x.md. - After the first successful post, retire the fallback Worker and close
tenzir/infra#307.
Every push to main runs workflows/rebuild-content.yaml. It sends a
news-updated repository dispatch to tenzir/content, which rebuilds
tenzir.com/changelog.
The workflow uses the same Tenzir GitHub App settings as synchronization and
requests a token scoped to the content repository.
Run these checks before committing changes to notification scripts or the X workflow:
uv run --with-requirements .github/scripts/requirements.txt \
python .github/scripts/test_changelog.py
uvx ruff check .github/scripts
uvx ruff format --check .github/scripts
git diff --checkWhen you change workflows/changelog-x.md, compile it with the pinned gh-aw
release and commit the resulting lock file.