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
2 changes: 0 additions & 2 deletions .github/scripts/opencode-target.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ export async function inspectTarget(event, mode, options) {
head_ref: "",
head_sha: "",
head_repo: "",
same_repo: "true",
}
}

Expand All @@ -136,7 +135,6 @@ export async function inspectTarget(event, mode, options) {
head_ref: pull.head?.ref,
head_sha: pull.head?.sha,
head_repo: pull.head?.repo?.full_name,
same_repo: String(sameRepo),
}
if (Object.values(result).some((value) => typeof value !== "string")) {
throw new Error("GitHub returned incomplete pull request metadata.")
Expand Down
5 changes: 2 additions & 3 deletions .github/scripts/opencode-target.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ test("classifies issues, PR conversations, and PR review comments", () => {
assert.deepEqual(classifyTarget({ pull_request: { number: 3 } }), { type: "pull_request", number: 3 })
})

test("issues do not call the API", async () => {
test("captures current issue metadata", async () => {
const result = await inspectTarget({ issue: { number: 4 } }, "build", { ...options, fetchImpl: response() })
assert.equal(result.target_type, "issue")
assert.equal(result.target_number, "4")
Expand All @@ -59,7 +59,6 @@ test("captures current pull request metadata", async () => {
head_ref: "feature",
head_sha: "abc123",
head_repo: repository,
same_repo: "true",
})
})

Expand Down Expand Up @@ -90,7 +89,7 @@ test("plan permits fork pull requests", async () => {
...options,
fetchImpl: response({ pullValue: { ...pull, head: { ...pull.head, repo: { full_name: "fork/repo" } } } }),
})
assert.equal(result.same_repo, "false")
assert.equal(result.head_repo, "fork/repo")
})

test("rejects oversized thread context before model access", async () => {
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/opencode.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ jobs:
head_ref: ${{ steps.target.outputs.head_ref }}
head_sha: ${{ steps.target.outputs.head_sha }}
head_repo: ${{ steps.target.outputs.head_repo }}
same_repo: ${{ steps.target.outputs.same_repo }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
Expand Down Expand Up @@ -118,9 +117,9 @@ jobs:
timeout-minutes: 45
environment: opencode-build
permissions:
contents: write
issues: write
pull-requests: write
contents: read
issues: read
pull-requests: read
id-token: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/workflow-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,18 @@ jobs:
run: |
set -euo pipefail
plan_job="$(awk '/^ plan:/{inside=1} /^ build:/{inside=0} inside' .github/workflows/opencode.yml)"
build_job="$(awk '/^ build:/{inside=1} inside' .github/workflows/opencode.yml)"
grep -q 'id-token: write' <<<"$plan_job"
if grep -qE 'GITHUB_TOKEN:|USE_GITHUB_TOKEN' <<<"$plan_job"; then
grep -q 'id-token: write' <<<"$build_job"
grep -q 'contents: read' <<<"$build_job"
grep -q 'issues: read' <<<"$build_job"
grep -q 'pull-requests: read' <<<"$build_job"
if grep -qE '(contents|issues|pull-requests): write' <<<"$build_job"; then
exit 1
fi
plan_run="$(awk '/^ - name: Run read-only OpenCode plan/{inside=1} inside {print} /run: opencode github run/{if (inside) exit}' .github/workflows/opencode.yml)"
build_run="$(awk '/^ - name: Run constrained OpenCode build/{inside=1} inside {print} /run: opencode github run/{if (inside) exit}' .github/workflows/opencode.yml)"
if grep -qE 'GITHUB_TOKEN:|USE_GITHUB_TOKEN|github\.token' <<<"$plan_run$build_run"; then
exit 1
fi
if grep -qE 'Configure OpenCode commit identity|github-actions\[bot\]' .github/workflows/opencode.yml; then
Expand Down
2 changes: 1 addition & 1 deletion .opencode/agent/github-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ permission:
"vault_pass.sh": deny
---

Implement the requested repository changes directly. Stay within the requested scope. Do not execute commands, alter agent or workflow policy, access external resources, commit, push, merge, or claim validation that was not performed. GitHub Actions will commit the resulting worktree and run repository validation for human review.
Implement the requested repository changes directly. Stay within the requested scope. Do not execute commands, alter agent or workflow policy, access external resources, commit, push, merge, or claim validation that was not performed. The native OpenCode GitHub App will publish the resulting worktree and GitHub Actions will run repository validation for human review.
11 changes: 5 additions & 6 deletions docs/opencode-github.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ OpenCode responds to explicit commands in GitHub issue comments, pull request co
- The build agent statically denies edits. A trusted startup plugin enables edits only for ordinary repository files and retains canonical and symlink path checks. If the plugin or config hook fails, editing stays denied.
- The build agent has no shell, network, subagent, external-directory, or merge access. It cannot edit `.github/`, `.opencode/`, `.git/`, `opencode.json`, `AGENTS.md`, `vault_pass.sh`, `.env`, or `.env.*` files.
- Comment workflows start from the trusted default branch before OpenCode processes PR content.
- Authorization caps all context sent by OpenCode at ten issue/review/review-thread comments, 100 changed files, and 60,000 characters before OAuth is exposed. GitHub attachment URLs are rejected rather than downloaded. It captures current target state, `updated_at`, PR base branch, head branch, head SHA, and head repository. After environment approval, build mode rechecks the commenter's permission and fetches the target again immediately before OpenCode, rejecting any relevant change.
- Authorization bounds the repository-collected issue and pull-request context to ten issue/review/review-thread comments, 100 changed files, and 60,000 characters before OAuth is exposed. Native OpenCode may independently fetch additional GitHub context. GitHub attachment URLs are rejected rather than downloaded. Authorization captures current target state, `updated_at`, PR base branch, head branch, head SHA, and head repository. After environment approval, build mode rechecks the commenter's permission and fetches the target again immediately before OpenCode, rejecting any relevant change.
- Builds may update any same-repository, non-default-branch PR head. A residual race remains: OpenCode v1.18.4 ultimately fetches and pushes by mutable branch name, so a branch can theoretically move after verification and before fetch or push. Execution is not immutable by SHA unless upstream changes this behavior; all such branches need equivalent protection where appropriate, and final human diff review remains mandatory.
- OpenCode sessions are never shared. The workflow downloads OpenCode v1.18.4 and verifies its pinned SHA-256 digest.
- OpenCode can create or update a branch and pull request but has no merge operation in its configured toolset. Protected branch rules provide the final merge boundary.
Expand All @@ -32,22 +32,21 @@ Do not use build mode until all settings in this section are active.
Create two GitHub environments:

- `opencode-plan` has no required reviewer and contains only its copy of the four OAuth component secrets.
- `opencode-build` has a required reviewer, prevents self-review, disables administrator bypass, and contains a separate copy of the four OAuth component secrets.
- `opencode-build` has a required reviewer, disables administrator bypass, and contains a separate copy of the four OAuth component secrets.

The installed OpenCode GitHub App must be installed only for this repository and have no branch/ruleset bypass. Both modes use the App through OIDC so App-created changes trigger normal pull-request validation workflows. The official App's installation token has App-defined permissions that cannot be reduced by per-job GitHub Actions permissions; owner-only plans remain read-only through the pinned OpenCode binary and the `github-plan` tool policy.

Create a ruleset or branch protection rule for `main` with:

- Pull requests required before merging.
- At least one human approving review and code owner review required.
- Code owner review enabled. The current zero-approval policy permits the repository owner to merge their own pull requests after required checks pass.
- Stale approvals dismissed when new commits are pushed.
- Approval required for the latest reviewable push.
- All conversations resolved before merging.
- `Ansible validation / validate` and `GitHub workflow validation / actionlint` required.
- `validate` and `actionlint` from GitHub Actions required.
- Force pushes and branch deletion blocked.
- Rule bypass disabled, including for administrators.

Do not enable the repository-wide "Allow GitHub Actions to create and approve pull requests" setting for OpenCode. The installed App creates pull requests instead. Required human approval, stale-review dismissal, latest-push approval, required checks, code ownership, and no bypass remain mandatory. Confirm neither workflows nor the App can satisfy required human review.
Do not enable the repository-wide "Allow GitHub Actions to create and approve pull requests" setting for OpenCode. The installed App creates pull requests instead. Required checks, code-owner settings, and no bypass remain mandatory. Confirm neither workflows nor the App can satisfy required human review when approvals are required.

Issues must be enabled and limited to collaborators. After every environment, secret, Actions, and branch/ruleset control is verified, create the Actions repository variable `OPENCODE_BUILD_ENABLED` with value `true` as the final activation step. Build remains skipped while this variable is absent or has any other value.

Expand Down
Loading