Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .autoducks/.installed.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
"schemaVersion": 1,
"source_repo": "deepducks/autoducks",
"channel": "stable",
"ref": "4ac745e52ee271fff7ef8b677cfb875c5572ae44",
"sha": "4ac745e52ee271fff7ef8b677cfb875c5572ae44",
"version": "0.5.8",
"installed_at": "2026-08-04T00:06:37Z",
"installed_by": "autoducks-update.yml#30864506984",
"ref": "0a09e02a45b55e8b0b9ad460a466a123be81fcee",
"sha": "0a09e02a45b55e8b0b9ad460a466a123be81fcee",
"version": "0.5.9",
"installed_at": "2026-08-04T16:12:33Z",
"installed_by": "autoducks-update.yml#30927991991",
"previous": {
"ref": "v0.5.2",
"sha": "b062c9a0ab871d9ee5dbb0bce1a430b27dbc36ee",
"version": "0.5.2"
"ref": "4ac745e52ee271fff7ef8b677cfb875c5572ae44",
"sha": "4ac745e52ee271fff7ef8b677cfb875c5572ae44",
"version": "0.5.8"
}
}
5 changes: 5 additions & 0 deletions .autoducks/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [0.5.9] - 2026-08-04

### Fixed
- fix(config): resolve the base branch from config or the repo, never a literal (#1182)

## [0.5.8] - 2026-08-03

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion .autoducks/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.8
0.5.9
18 changes: 18 additions & 0 deletions .autoducks/design/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,24 @@ All branches follow a predictable convention rooted in issue IDs. The prefix enc

The Maestro's PR-merged re-trigger listens on both `feature/*` and `fix/*`. The fix-utility `-fix-<epoch>` suffix is unrelated to the `fix/` prefix. The Agent lane's `agent/<name>/…` branches are outside the pipeline (see [Agent Lane](#agent-lane)) and are never watched by the Maestro.

### Where the base branch comes from

Two sources, in this order, and **never a literal** (#1181):

1. **`defaults.base_branch`** in `autoducks.json` — the explicit operator override. Set it when the pipeline should run off a branch that is not the repository's default.
2. **The repository's own default branch** — `github.event.repository.default_branch` in a workflow expression, `gh api repos/$REPO --jq .default_branch` in a script.

`main` was previously hardcoded as the fallback in `autoducks-commit-lint.yml` and `autoducks-developer.yml`, so a repo on `master` got a push trigger that never fired and a checkout of a ref that did not exist — both silently, because a trigger that does not fire is indistinguishable from one with nothing to report.

**`AUTODUCKS_BASE_BRANCH` carries source 1 only, and may be empty.** `load-config.sh` exports the configured value verbatim and does not resolve step 2; the caller does, because the caller knows whether it can reach the host. [`sync-child-gitlinks.sh`](../core/orchestration/sync-child-gitlinks.sh) is the reference shape: config, then the host API, then a warning and a clean exit rather than acting on a branch named `""`.

Resolving step 2 inside `load-config.sh` is a tempting simplification and a mistake. The only source available there without a token is `origin/HEAD`, a local ref that can be stale or absent, and populating the variable from it silently preempts the authoritative answer the caller was about to fetch.

Two consequences worth knowing:

- **The push trigger cannot express this.** `on.push.branches` takes glob patterns only, never an expression. `autoducks-commit-lint.yml` therefore subscribes broadly, excludes pipeline branch prefixes, and makes the default-branch decision in the job's `if:`, where the value is reachable. Same shape as `autoducks-delivery-check.yml`, and for the same reason: exclusion filters survive a rename, allow-lists do not.
- **The Agent lane reads source 2, not source 1.** `AUTODUCKS_BASE_REF` is built from the repository default branch, deliberately: the lane's security premise is "merged, reviewed repo content" (see [Agent Lane](#agent-lane)), and the repository default is the better answer to that than a config key any contributor could edit. On a repo where the two disagree, custom agent definitions come from the repository default while every other lane follows the configured value. `setup.sh` check 16 reports the divergence rather than resolving it, since a deliberate split is legitimate.

---

## Metarepo mode (submodule aggregation)
Expand Down
34 changes: 28 additions & 6 deletions .autoducks/runtimes/github-actions/autoducks-commit-lint.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
name: 'Autoducks: Commit-message Lint'

# The push trigger cannot name the default branch: `on.push.branches` takes
# glob patterns only, never an expression, so `github.event.repository.default_branch`
# is unavailable here. It used to say `branches: [main]`, which meant this
# workflow never ran on a repo whose branch is `master` (#1181) — silently, since
# a trigger that does not fire looks identical to one with nothing to report.
#
# So: subscribe to pushes broadly, exclude the pipeline's own task-branch noise
# by prefix, and make the real decision in the job's `if:` below, where the
# default branch IS reachable. Same shape autoducks-delivery-check.yml uses, and
# for the same reason — exclusion filters survive a rename, allow-lists do not.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]
branches-ignore:
- 'feature/**'
- 'fix/**'
- 'autoducks/**'

permissions:
id-token: write
Expand All @@ -15,12 +28,21 @@ jobs:
commit-lint:
name: commit-message lint
runs-on: ubuntu-latest
# Skip PRs a parent metarepo opened on this child (marker in body / label):
# the metarepo already reviewed the aggregate change, and this child's own
# pipeline is meant to stay dormant for it. push:main still lints normally.
# Two independent gates.
#
# On push: lint only the default branch. This is the branch check the
# trigger could not express — `github.ref` against the repository's own
# default_branch, so it follows a rename instead of pinning a literal.
#
# On pull_request: skip PRs a parent metarepo opened on this child (marker
# in body / label). The metarepo already reviewed the aggregate change, and
# this child's own pipeline is meant to stay dormant for it. A push to the
# default branch still lints normally.
if: >-
github.event_name == 'push' ||
(!contains(github.event.pull_request.body, 'autoducks:metarepo-managed') &&
(github.event_name == 'push' &&
github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) ||
(github.event_name == 'pull_request' &&
!contains(github.event.pull_request.body, 'autoducks:metarepo-managed') &&
!contains(github.event.pull_request.labels.*.name, 'Autoducks:external'))
steps:
- name: Mint app token
Expand Down
12 changes: 9 additions & 3 deletions .autoducks/runtimes/github-actions/autoducks-developer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ on:
required: true
type: string
base_branch:
required: true
# No literal default (#1181): 'main' here was wrong on any repo that
# does not have that branch. Empty means "use the repository's default
# branch", resolved at the checkout step below.
required: false
type: string
default: 'main'
default: ''
actor:
required: false
type: string
Expand Down Expand Up @@ -92,7 +95,10 @@ jobs:
echo "AUTODUCKS_APP_TOKEN=$TOKEN" >> "$GITHUB_ENV"
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.base_branch || 'main' }}
# An explicit dispatch input wins; otherwise the repository's own
# default branch. Never a literal (#1181) — 'main' here checked out a
# ref that does not exist on a repo whose branch is 'master'.
ref: ${{ inputs.base_branch != '' && inputs.base_branch || github.event.repository.default_branch }}
fetch-depth: 0
submodules: recursive
token: ${{ steps.apptoken.outputs.value || secrets.AUTODUCKS_PAT || secrets.GITHUB_TOKEN }}
Expand Down
34 changes: 28 additions & 6 deletions .github/workflows/autoducks-commit-lint.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
name: 'Autoducks: Commit-message Lint'

# The push trigger cannot name the default branch: `on.push.branches` takes
# glob patterns only, never an expression, so `github.event.repository.default_branch`
# is unavailable here. It used to say `branches: [main]`, which meant this
# workflow never ran on a repo whose branch is `master` (#1181) — silently, since
# a trigger that does not fire looks identical to one with nothing to report.
#
# So: subscribe to pushes broadly, exclude the pipeline's own task-branch noise
# by prefix, and make the real decision in the job's `if:` below, where the
# default branch IS reachable. Same shape autoducks-delivery-check.yml uses, and
# for the same reason — exclusion filters survive a rename, allow-lists do not.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]
branches-ignore:
- 'feature/**'
- 'fix/**'
- 'autoducks/**'

permissions:
id-token: write
Expand All @@ -15,12 +28,21 @@ jobs:
commit-lint:
name: commit-message lint
runs-on: ubuntu-latest
# Skip PRs a parent metarepo opened on this child (marker in body / label):
# the metarepo already reviewed the aggregate change, and this child's own
# pipeline is meant to stay dormant for it. push:main still lints normally.
# Two independent gates.
#
# On push: lint only the default branch. This is the branch check the
# trigger could not express — `github.ref` against the repository's own
# default_branch, so it follows a rename instead of pinning a literal.
#
# On pull_request: skip PRs a parent metarepo opened on this child (marker
# in body / label). The metarepo already reviewed the aggregate change, and
# this child's own pipeline is meant to stay dormant for it. A push to the
# default branch still lints normally.
if: >-
github.event_name == 'push' ||
(!contains(github.event.pull_request.body, 'autoducks:metarepo-managed') &&
(github.event_name == 'push' &&
github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) ||
(github.event_name == 'pull_request' &&
!contains(github.event.pull_request.body, 'autoducks:metarepo-managed') &&
!contains(github.event.pull_request.labels.*.name, 'Autoducks:external'))
steps:
- name: Mint app token
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/autoducks-developer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ on:
required: true
type: string
base_branch:
required: true
# No literal default (#1181): 'main' here was wrong on any repo that
# does not have that branch. Empty means "use the repository's default
# branch", resolved at the checkout step below.
required: false
type: string
default: 'main'
default: ''
actor:
required: false
type: string
Expand Down Expand Up @@ -92,7 +95,10 @@ jobs:
echo "AUTODUCKS_APP_TOKEN=$TOKEN" >> "$GITHUB_ENV"
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.base_branch || 'main' }}
# An explicit dispatch input wins; otherwise the repository's own
# default branch. Never a literal (#1181) — 'main' here checked out a
# ref that does not exist on a repo whose branch is 'master'.
ref: ${{ inputs.base_branch != '' && inputs.base_branch || github.event.repository.default_branch }}
fetch-depth: 0
submodules: recursive
token: ${{ steps.apptoken.outputs.value || secrets.AUTODUCKS_PAT || secrets.GITHUB_TOKEN }}
Expand Down
Loading
Loading