Skip to content

feat(runtime): implement OpenCodeRuntime - #7103

Open
yvonnedevlinrh wants to merge 2 commits into
fullsend-ai:mainfrom
yvonnedevlinrh:feat/opencode-runtime-510
Open

feat(runtime): implement OpenCodeRuntime #7103
yvonnedevlinrh wants to merge 2 commits into
fullsend-ai:mainfrom
yvonnedevlinrh:feat/opencode-runtime-510

Conversation

@yvonnedevlinrh

Copy link
Copy Markdown

Summary

Adds OpenCode as an opt-in Fullsend agent runtime while keeping Claude Code as the default.

OpenCode is currently restricted to read-only agents until the sandbox security-hook adapter tracked by unbound-force/unbound-force#515 is implemented.

Related Issue

Related to #1260.

Downstream implementation tracker: unbound-force/unbound-force#510

Changes Made

  • Adds OpenCode bootstrap, execution, configuration, and transcript handling.
  • Translates agent definitions and tool permissions into OpenCode format.
  • Supports model aliases, provider/model references, and effort via --variant.
  • Captures NDJSON output, runtime metrics, debug logs, and transcript errors.
  • Uses a runner-owned configuration directory outside the writable workspace.
  • Adds OpenCode to runtime selection, CLI help, configuration validation, and documentation.
  • Adds the required Vertex AI egress binary mappings.
  • Preserves Codex, Pi, dummy, and dummy-playback behavior from current main.
  • Adds unit tests covering bootstrap, execution, parsing, configuration, and transcript extraction.

Security

OpenCode does not yet install sandbox tool hooks. Write-capable agents remain gated until the hook adapter lands.

Host-side scanning and sandbox egress controls continue to apply.

Testing

  • make lint-all
  • Focused tests for internal/runtime, internal/cli, internal/config, internal/repos, and internal/scaffold
  • Approximate patch coverage: 89.9%
  • Verified against OpenCode CLI v1.18.26

Full local Go testing encountered pre-existing environment-sensitive failures tracked in #7078 and #7079. The arm64 vendor-test failures do not affect amd64 CI.

make e2e-test was not run because it requires live GitHub pool credentials.

Backward Compatibility

This is additive and opt-in. Existing configurations continue to use Claude Code unless runtime: opencode is selected.

@yvonnedevlinrh
yvonnedevlinrh requested a review from a team as a code owner September 8, 2026 12:49
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

E2E tests are running

Authorization passed for this commit. See the E2E Tests workflow for results.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Implement OpenCode as an opt-in agent runtime

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Implements OpenCode bootstrap, execution, model translation, metrics, debugging, and transcript
 handling.
• Enables opt-in runtime selection while retaining Claude Code as the default.
• Documents read-only security limitations and adds focused runtime, configuration, and egress
 tests.
Diagram

graph TD
  Select["CLI and Config"] --> Registry["Runtime Registry"] --> Bootstrap["OpenCode Bootstrap"] --> Config["Runner Config"] --> CLI["OpenCode CLI"] --> Parser["NDJSON Parser"] --> Output["Metrics and Artifacts"]
  Egress["Vertex Egress"] --> CLI
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Defer until sandbox hooks are complete
  • ➕ Ships OpenCode with the same sandbox tool-hook guarantees as established runtimes.
  • ➕ Avoids an interim read-only operational restriction and reserved empty adapter contract.
  • ➖ Blocks early runtime validation and read-only pilots.
  • ➖ Couples execution, transcript, and provider work to the separate hook-adapter schedule.
2. Use exported OpenCode sessions instead of stream teeing
  • ➕ Could provide richer, full-fidelity session artifacts.
  • ➕ May reduce dependence on synthesized completion state from the live event stream.
  • ➖ Adds scope and delays the runtime rollout.
  • ➖ Post-run export alone would not provide live progress and metrics handling.
  • ➖ Requires the separate transcript redesign already tracked by unbound-force#513.

Recommendation: Keep the phased implementation: runner-owned configuration, live NDJSON parsing, and interim transcript teeing provide a practical foundation without coupling this PR to two downstream adapters. The rollout should remain limited to read-only use until unbound-force#515 lands; that restriction should be enforced operationally or in runtime selection rather than relying only on documentation.

Files changed (38) +1681 / -232

Enhancement (12) +836 / -82
admin.goExpose OpenCode in admin runtime help +1/-1

Expose OpenCode in admin runtime help

• Updates the administrative setup flag help with OpenCode and the supported dummy runtimes.

internal/cli/admin.go

agent.goExpose OpenCode for per-agent configuration +1/-1

Expose OpenCode for per-agent configuration

• Adds OpenCode to the documented values accepted by the agent runtime flag.

internal/cli/agent.go

github.goExpose OpenCode during GitHub setup +1/-1

Expose OpenCode during GitHub setup

• Adds OpenCode to the per-repository runtime flag guidance.

internal/cli/github.go

repos.goExpose OpenCode for repository installs +1/-1

Expose OpenCode for repository installs

• Adds OpenCode to the repository manifest runtime flag help.

internal/cli/repos.go

run.goExpose OpenCode run and model overrides +2/-2

Expose OpenCode run and model overrides

• Adds OpenCode to runtime override help and documents its provider/model support.

internal/cli/run.go

runtime_prompt.goOffer OpenCode in the interactive runtime prompt +5/-4

Offer OpenCode in the interactive runtime prompt

• Adds OpenCode as an experimental human-selectable runtime and communicates its read-only limitation.

internal/cli/runtime_prompt.go

config.goMake OpenCode a valid configured runtime +5/-5

Make OpenCode a valid configured runtime

• Adds OpenCode to the recognized runtime set for organization, repository, and agent configuration.

internal/config/config.go

opencode.goReplace the OpenCode runtime stub +57/-64

Replace the OpenCode runtime stub

• Defines the implemented runtime metadata, runner-owned configuration directory, required environment exports, and debug-log capability.

internal/runtime/opencode.go

opencode_bootstrap.goBootstrap OpenCode agents and skills +257/-0

Bootstrap OpenCode agents and skills

• Creates the runner-owned OpenCode layout, translates Claude-style agent definitions and tool permissions, uploads skills, warns on unsupported plugins, and preflights the CLI binary.

internal/runtime/opencode_bootstrap.go

opencode_run.goExecute OpenCode inside the sandbox +362/-0

Execute OpenCode inside the sandbox

• Implements model alias and provider resolution, secure command construction, effort variants, prompt overrides, stream teeing, metrics, debug capture, artifact cleanup, and reserved hook-integrity checks.

internal/runtime/opencode_run.go

opencode_transcript.goExtract and validate OpenCode artifacts +141/-0

Extract and validate OpenCode artifacts

• Downloads sandbox-side JSONL transcripts and debug logs, replays streams for error detection, and emits standard transcript summaries.

internal/runtime/opencode_transcript.go

registry.goRecognize OpenCode as user-selectable +3/-3

Recognize OpenCode as user-selectable

• Updates registry validation documentation now that OpenCode belongs to the configured runtime set.

internal/runtime/registry.go

Tests (12) +674 / -112
run_overrides_test.goTreat OpenCode as a valid run override +6/-6

Treat OpenCode as a valid run override

• Replaces former OpenCode rejection cases with genuinely unknown runtime values.

internal/cli/run_overrides_test.go

runtime_binaries_test.goMap OpenCode to Vertex egress binaries +3/-0

Map OpenCode to Vertex egress binaries

• Adds OpenCode executable patterns to the expected Vertex AI runtime egress mapping.

internal/cli/runtime_binaries_test.go

runtime_prompt_test.goTest interactive OpenCode selection +4/-3

Test interactive OpenCode selection

• Verifies OpenCode is accepted by the runtime prompt and appears in validation guidance.

internal/cli/runtime_prompt_test.go

config_test.goTest OpenCode configuration validation +9/-15

Test OpenCode configuration validation

• Updates runtime validation tests to accept OpenCode across organization, repository, and agent settings while preserving unknown-runtime rejection.

internal/config/config_test.go

manifest_edit_test.goPreserve unknown runtime rejection coverage +1/-1

Preserve unknown runtime rejection coverage

• Replaces the now-valid OpenCode rejection case with a nonexistent runtime.

internal/repos/manifest_edit_test.go

manifest_test.goUpdate manifest validation for OpenCode +2/-2

Update manifest validation for OpenCode

• Uses an unknown runtime for negative validation now that OpenCode is supported.

internal/repos/manifest_test.go

capabilities_test.goVerify OpenCode debug-log capability +8/-4

Verify OpenCode debug-log capability

• Confirms OpenCode supplies its runtime-specific debug filename while retaining fallback and precedence coverage through a neutral test runtime.

internal/runtime/capabilities_test.go

opencode_bootstrap_test.goTest OpenCode bootstrap and execution integration +331/-0

Test OpenCode bootstrap and execution integration

• Covers agent translation, skill upload, preflight failures, transcript downloads, streamed execution, metrics, cleanup, and error-exit correction using fake sandbox commands.

internal/runtime/opencode_bootstrap_test.go

opencode_test.goTest OpenCode runtime behavior +255/-29

Test OpenCode runtime behavior

• Adds coverage for metadata, environment exports, registry resolution, model and tool translation, command safety, hook guards, transcript parsing, and debug handling.

internal/runtime/opencode_test.go

registry_test.goTest OpenCode runtime resolution paths +52/-49

Test OpenCode runtime resolution paths

• Verifies OpenCode resolves from organization, repository, and per-agent configuration while unknown runtimes remain rejected.

internal/runtime/registry_test.go

scaffold_test.goLock the OpenCode Vertex binary allowlist +1/-1

Lock the OpenCode Vertex binary allowlist

• Extends scaffold validation to require both OpenCode executable patterns in the Vertex profile.

internal/scaffold/scaffold_test.go

runtime_test.goAccept OpenCode in behavior-test validation +2/-2

Accept OpenCode in behavior-test validation

• Replaces former OpenCode rejection fixtures with nonexistent runtime values while retaining invalid-setting coverage.

pkg/behaviourtest/steps/runtime_test.go

Documentation (12) +158 / -37
config.tsAdd OpenCode to the runtime navigation +1/-0

Add OpenCode to the runtime navigation

• Adds the OpenCode runtime guide to the documentation sidebar.

docs/.vitepress/config.ts

agent.mdDocument OpenCode agent overrides +1/-1

Document OpenCode agent overrides

• Lists OpenCode as a supported value for per-agent runtime selection.

docs/cli/agent.md

github.mdDocument OpenCode GitHub setup selection +1/-1

Document OpenCode GitHub setup selection

• Adds OpenCode to the GitHub setup runtime choices and clarifies dummy runtime usage.

docs/cli/github.md

repos.mdDocument OpenCode repository manifest settings +2/-2

Document OpenCode repository manifest settings

• Adds OpenCode to repository installation and default runtime documentation.

docs/cli/repos.md

run.mdDocument OpenCode run overrides and metrics +3/-3

Document OpenCode run overrides and metrics

• Adds OpenCode to run-time overrides, metrics examples, and iteration timeout coverage.

docs/cli/run.md

runtime-implementation.mdDescribe OpenCode security and artifact support +16/-15

Describe OpenCode security and artifact support

• Updates the runtime capability matrix for the implemented OpenCode backend. It explicitly records that host controls apply while sandbox tool hooks remain unavailable pending the plugin adapter.

docs/contributing/runtime-implementation.md

cli-internals.mdExpose OpenCode in the CLI command map +1/-1

Expose OpenCode in the CLI command map

• Updates the internal CLI reference with OpenCode as a per-agent runtime option.

docs/guides/dev/cli-internals.md

choosing-a-runtime.mdAdd OpenCode to runtime selection guidance +1/-1

Add OpenCode to runtime selection guidance

• Shows OpenCode as a valid runtime when changing repository setup.

docs/guides/getting-started/choosing-a-runtime.md

configuring-github.mdMark OpenCode as an experimental runtime +1/-1

Mark OpenCode as an experimental runtime

• Adds OpenCode to the experimental runtimes available during GitHub setup.

docs/guides/getting-started/configuring-github.md

runtimes.mdIntegrate OpenCode into the runtime comparison +16/-10

Integrate OpenCode into the runtime comparison

• Promotes OpenCode from a stub to an experimental runtime and documents model, effort, tools, skills, configuration, artifacts, and security-hook behavior.

docs/runtimes.md

opencode.mdAdd the OpenCode runtime guide +113/-0

Add the OpenCode runtime guide

• Introduces usage, provider configuration, runner-owned config discovery, local execution, artifacts, behavior differences, and current read-only security limitations.

docs/runtimes/opencode.md

manifest.goDocument OpenCode manifest support +2/-2

Document OpenCode manifest support

• Updates repository manifest field comments to include OpenCode as a supported runtime.

internal/repos/manifest.go

Other (2) +13 / -1
sandbox.goDefine the runner-owned OpenCode config path +10/-0

Define the runner-owned OpenCode config path

• Adds a sandbox constant for OpenCode configuration outside the agent-writable workspace.

internal/sandbox/sandbox.go

fullsend-vertex-ai.yamlPermit OpenCode through Vertex AI egress +3/-1

Permit OpenCode through Vertex AI egress

• Adds OpenCode executable patterns to the Vertex inference profile and updates its runtime description.

internal/scaffold/fullsend-repo/profiles/fullsend-vertex-ai.yaml

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (7) 📘 Rule violations (2) 🔗 Cross-repo conflicts (4) 📜 Skill insights (1)

Grey Divider


Action required

1. Readers mistake deferred work as ready 📜 Skill insight ≡ Correctness
Description
docs/runtimes/opencode.md presents the unimplemented security-hook adapter under an Experimental
blockquote rather than the required > **Planned:** callout. The same deferred adapter governs
write-path denial and sandbox hooks, so readers encounter future behavior in several capability
descriptions without the standardized status marker.
Code

docs/runtimes/opencode.md[R14-17]

+> **Experimental — read-only agents only.** OpenCode is enabled for read-only agents (`triage`,
+> `prioritize`). Write-capable agents (`code`, `fix`) are gated on the security-hook adapter, tracked
+> in [unbound-force#515](https://github.com/unbound-force/unbound-force/issues/515): OpenCode has no
+> native PreToolUse/PostToolUse hooks, so until the runner-owned, sha256-gated plugin adapter lands,
Relevance

●●● Strong

Explicit documentation-format rules and the deferred feature status make the standardized Planned
callout a straightforward correction.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062082 mandates the exact > **Planned:** blockquote format and an issue link for
not-yet-implemented features. The added documentation links the tracking issue but labels the
missing adapter as experimental or describes it inline instead of using the required marker.

docs/runtimes/opencode.md[14-18]
docs/runtimes/opencode.md[41-44]
docs/contributing/runtime-implementation.md[157-175]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Documentation mentions the unimplemented OpenCode hook adapter without the required planned-feature callout format.

## Issue Context
Convert each mention of deferred adapter, write-denial, hook, or transcript work into a `> **Planned:**` blockquote containing the relevant linked issue, while keeping current capabilities distinct from future ones.

## Fix Focus Areas
- docs/runtimes/opencode.md[14-18]
- docs/runtimes/opencode.md[41-44]
- docs/runtimes/opencode.md[96-98]
- docs/contributing/runtime-implementation.md[157-175]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Every OpenCode run exits before starting 🐞 Bug ≡ Correctness
Description
OpenCodeRuntime.EnvExports adds OPENCODE_CONFIG_CONTENT and GOOGLE_APPLICATION_CREDENTIALS as
bare lines, and bootstrapEnv writes those lines verbatim into the sourced .env file. POSIX sh
treats each bare name as a command, so . /sandbox/workspace/.env fails with command-not-found and
the subsequent && chain never invokes opencode.
Code

internal/runtime/opencode.go[R71-73]

+		fmt.Sprintf("export OPENCODE_CONFIG_DIR=%s", r.ConfigDir()),
+		"OPENCODE_CONFIG_CONTENT",        // Vertex provider + permission denials (merges last)
+		"GOOGLE_APPLICATION_CREDENTIALS", // WIF credential file
Relevance

●●● Strong

Bare environment names sourced as shell commands cause deterministic startup failure before OpenCode
executes.

PR-#3820

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new runtime returns two unqualified variable names, whereas bootstrap writes runtime entries
directly to .env; OpenCode then sources that file before its command. A bare shell word is
executed as a command rather than exporting an inherited variable, and the && sequence aborts on
that failure.

internal/runtime/opencode.go[69-74]
internal/cli/run.go[2935-2950]
internal/runtime/opencode_run.go[135-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
OpenCode runtime environment entries are emitted as command names rather than shell exports, causing the sourced sandbox `.env` file to fail before OpenCode starts.

## Issue Context
`bootstrapEnv` appends each runtime export verbatim and `Run` sources that file in an `&&` chain.

## Fix Focus Areas
- internal/runtime/opencode.go[69-74]
- internal/cli/run.go[2935-2950]
- internal/runtime/opencode_run.go[135-138]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Secure OpenCode runs never start 🐞 Bug ☼ Reliability
Description
buildOpenCodeRunCommand enables the hook-integrity guard whenever HooksSettingsPath is
non-empty, but the new runtime supplies neither an adapter file nor adapter bytes for the required
hash. Security is enabled by default and the runner sets that path in this case, so ordinary
OpenCode runs exit 97 before sourcing .env or executing the agent.
Code

internal/runtime/opencode_run.go[R128-134]

+	if hooksEnabled {
+		// Before .env: that file is agent-writable and could otherwise shadow
+		// the guard's tools with functions or a PATH entry. #515 supplies the
+		// embedded adapter bytes; until then the guard is only emitted when
+		// the runner signals hooks are on, and #515 wires the real hash.
+		prelude = append(prelude, "&& "+openCodeHooksGuard(r.openCodeHooksExtensionPath()))
+	}
Relevance

●●● Strong

The default security guard requires an adapter that this PR explicitly does not install, so runs
fail closed immediately.

PR-#6756

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The runner sets HooksSettingsPath when security is enabled, which defaults to true. The added
command inserts a guard in that case, while the guard requires an on-disk file matching the hash of
a nil adapter and Bootstrap explicitly installs nothing.

internal/cli/run.go[2211-2228]
internal/harness/harness.go[230-236]
internal/runtime/opencode_run.go[128-138]
internal/runtime/opencode_run.go[240-251]
internal/runtime/opencode_bootstrap.go[60-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The OpenCode launch command enables a fail-closed adapter guard even though this PR intentionally does not install the adapter. Default security-enabled runs therefore fail before OpenCode executes.

## Issue Context
The adapter is explicitly deferred to the follow-up implementation, while security is enabled by default and supplies `HooksSettingsPath` to every runtime.

## Fix Focus Areas
- internal/runtime/opencode_run.go[128-134]
- internal/runtime/opencode_run.go[240-251]
- internal/runtime/opencode_bootstrap.go[60-66]
- internal/cli/run.go[2211-2228]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (4)
4. First-party models stay unconfigured 🔗 Cross-repo conflict ≡ Correctness
Description
OpenCodeRuntime.EnvExports references OPENCODE_CONFIG_CONTENT, but neither the runtime bootstrap
nor the agents repository constructs the required anthropic-vertex provider and permission policy.
The first-party harnesses provide only the existing Vertex environment and credentials, so selecting
OpenCode leaves its default model reference without the configuration needed for inference.
Code

internal/runtime/opencode.go[R72-73]

+		"OPENCODE_CONFIG_CONTENT",        // Vertex provider + permission denials (merges last)
+		"GOOGLE_APPLICATION_CREDENTIALS", // WIF credential file
Relevance

●●● Strong

The runtime declares required configuration but does not construct or deliver the provider
configuration needed for inference.

PR-#6467

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR declares that the harness must deliver the OpenCode provider configuration and exports only
its variable name, while Bootstrap creates directories and translated artifacts without generating
that content. The agents harness mounts only its existing GCP environment and credential files,
whose variables contain no OpenCode provider configuration.

internal/runtime/opencode.go[53-74]
internal/runtime/opencode_bootstrap.go[96-139]
External repo: fullsend-ai/agents, harness/triage.yaml [23-31]
External repo: fullsend-ai/agents, env/gcp-vertex.env [1-5]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Generate and export the OpenCode provider and permission configuration instead of assuming first-party harnesses already supply `OPENCODE_CONFIG_CONTENT`.

## Issue Context
The agents repository currently supplies Vertex credentials and Claude-oriented variables only. Prefer constructing runner-owned OpenCode configuration in this repository; otherwise coordinate explicit configuration across every agents harness.

## Fix Focus Areas
- internal/runtime/opencode.go[53-74]
- internal/runtime/opencode_bootstrap.go[96-139]
- harness/triage.yaml[23-31]
- env/gcp-vertex.env[1-5]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. First-party images lack OpenCode 🔗 Cross-repo conflict ☼ Reliability
Description
openCodePreflightVersion now requires opencode --version to succeed, but the sandbox image
source installs the other runtimes without installing OpenCode. The agents repository pins that
image family by digest in its triage and prioritize harnesses, so either advertised first-party
OpenCode pilot fails during bootstrap before an iteration begins.
Code

internal/runtime/opencode_bootstrap.go[R248-250]

+	stdout, stderr, exitCode, err := sandbox.Exec(sandboxName, "opencode --version", 30*time.Second)
+	if err != nil {
+		return fmt.Errorf("opencode preflight: %w", err)
Relevance

●●● Strong

Bootstrap preflights an executable absent from the cited image, deterministically preventing
advertised first-party runs.

PR-#6035

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new bootstrap fails when the OpenCode executable is absent, while the image source shown
installs Claude Code and pi but has no corresponding OpenCode installation. Both first-party agents
advertised for the pilot remain pinned to published image digests from that image family.

internal/runtime/opencode_bootstrap.go[243-256]
images/sandbox/Containerfile[49-60]
images/sandbox/Containerfile[81-104]
External repo: fullsend-ai/agents, harness/triage.yaml [11-18]
External repo: fullsend-ai/agents, harness/prioritize.yaml [11-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Install a reviewed, pinned OpenCode CLI version in the sandbox images before making the runtime selectable.

## Issue Context
After publishing updated images, coordinate updates to the image digests used by the first-party triage and prioritize harnesses.

## Fix Focus Areas
- images/sandbox/Containerfile[49-104]
- internal/runtime/opencode_bootstrap.go[243-256]
- harness/triage.yaml[11-18]
- harness/prioritize.yaml[11-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Vertex blocks first-party inference 🔗 Cross-repo conflict ≡ Correctness
Description
The scaffold Vertex profile adds OpenCode binary patterns, but the agents repository's profile
actually selected by first-party harnesses still permits only Claude, pi, and Node. Triage and
prioritize therefore reach the sandbox with a profile that denies OpenCode's Vertex requests even
after an image and provider configuration are supplied.
Code

internal/scaffold/fullsend-repo/profiles/fullsend-vertex-ai.yaml[R25-26]

+  - "**/opencode"
+  - "**/opencode.exe"
Relevance

●●● Strong

Scaffold profile drift affecting runtime binaries is actionable; historical scaffold consistency
fixes were accepted.

PR-#390

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed scaffold profile permits both OpenCode executable names, but the release-coupled agents
profile omits them. The first-party triage and prioritize harnesses explicitly select the stale
agents profile.

internal/scaffold/fullsend-repo/profiles/fullsend-vertex-ai.yaml[21-27]
External repo: fullsend-ai/agents, profiles/fullsend-vertex-ai.yaml [28-32]
External repo: fullsend-ai/agents, harness/triage.yaml [14-18]
External repo: fullsend-ai/agents, harness/prioritize.yaml [14-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Add the OpenCode executable patterns to the Vertex profile enforced by the agents repository.

## Issue Context
The scaffold copy and fleet copy are release-coupled. Update and validate both before advertising OpenCode for first-party agents.

## Fix Focus Areas
- internal/scaffold/fullsend-repo/profiles/fullsend-vertex-ai.yaml[21-27]
- profiles/fullsend-vertex-ai.yaml[28-32]
- harness/triage.yaml[14-18]
- harness/prioritize.yaml[14-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Write agents run without safeguards 🔗 Cross-repo conflict ⛨ Security
Description
Adding opencode to ValidRuntimes makes it selectable through repository, per-agent, and
command-line overrides, while ResolveForAgent checks neither the agent role nor ReadonlyRepo,
even though OpenCode installs no sandbox hook adapter and enables Write, Edit, and Bash. When
selected for the first-party code or fix agents, or any unrestricted custom agent in a normally
writable harness, execution reaches write-capable tools without the intended read-only rollout gate.
Code

internal/config/config.go[322]

+	return []string{"claude", "pi", "codex", "opencode", "dummy", "dummy-playback"}
Relevance

●●● Strong

Enabling a runtime without enforcing its documented read-only limitation is a concrete security
boundary failure.

PR-#6756

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The configuration change places OpenCode in the universal valid-runtime set, and runtime resolution
validates only the runtime name, allowing repository configuration, per-agent settings, and
--runtime opencode overrides to select it. The runner makes a checkout read-only only when the
independent ReadonlyRepo option is enabled, whose default is false, while OpenCode’s tool
translation enables Write, Edit, and Bash and its bootstrap explicitly omits security-hook
wiring. The code and fix agent definitions confirm that these first-party agents modify, test,
and commit repository content, demonstrating that the documented read-only limitation is not
enforced before write-capable execution.

Rule 2889480: Consult runtime implementation guide when modifying runtime.Runtime backends
internal/config/config.go[317-323]
internal/runtime/registry.go[69-96]
internal/runtime/opencode_bootstrap.go[60-66]
internal/cli/run.go[2211-2234]
internal/runtime/opencode_bootstrap.go[60-67]
internal/runtime/opencode_bootstrap.go[217-228]
internal/config/config.go[317-322]
internal/harness/harness.go[339-345]
internal/cli/run.go[1972-1990]
internal/runtime/opencode_bootstrap.go[179-228]
docs/contributing/runtime-implementation.md[157-175]
docs/runtimes/opencode.md[14-18]
External repo: fullsend-ai/agents, agents/code.md [2-17]
External repo: fullsend-ai/agents, agents/fix.md [2-17]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Reject OpenCode for write-capable or unrestricted agents until its sandbox hook adapter is installed and integrity-checked. Ensure repository configuration, per-agent configuration, and command-line runtime overrides all enforce the documented read-only limitation before sandbox execution.

## Issue Context
Configuration validation alone cannot identify whether an agent is safe for the OpenCode pilot. Runtime resolution currently accepts OpenCode for any named agent, `ReadonlyRepo` is an independent harness setting that defaults to false, and the OpenCode bootstrap enables write, edit, and bash tools while intentionally installing no hook adapter. Enforce eligibility at a central boundary where the resolved agent and harness capabilities are available, including the intended triage/prioritize or explicitly read-only cases, and add coverage for the first-party `code` and `fix` agents as well as unrestricted custom agents across every runtime-selection path.

## Fix Focus Areas
- internal/config/config.go[317-323]
- internal/runtime/registry.go[69-96]
- internal/cli/run.go[1011-1035]
- internal/cli/run.go[1972-1990]
- internal/runtime/opencode_bootstrap.go[60-66]
- internal/runtime/opencode_bootstrap.go[179-228]
- agents/code.md[2-17]
- agents/fix.md[2-17]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

8. Run artifacts can expose credentials 📘 Rule violation ⛨ Security
Description
Run tees the raw OpenCode event stream directly into OutputPath, and ExtractTranscripts
downloads the second raw copy without applying credential-literal or secret-pattern redaction. When
model or tool output contains a token, key, password, or credential-bearing command output, both
persisted artifacts receive it unchanged.
Code

internal/runtime/opencode_run.go[R281-283]

+			defer f.Close()
+			reader = io.TeeReader(stdout, f)
+		}
Relevance

●●● Strong

Recent OpenCode precedent accepted secret redaction for untrusted tool output and artifact exposure.

PR-#6147
PR-#3186

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 3092695 requires external content written to logs or files to use the credential-redaction
guidance. The new runtime stores its unfiltered NDJSON stream in a host output file and later
downloads the unfiltered sandbox copy as a transcript.

Rule 3092695: Consult Go credential redaction guide for external-facing code changes
internal/runtime/opencode_run.go[275-283]
internal/runtime/opencode_transcript.go[54-67]
docs/contributing/go-code.md[417-454]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
OpenCode output and transcript artifacts are persisted without the repository's required credential-redaction passes.

## Issue Context
Apply sensitive runner-environment literal replacement and `security.SecretRedactor` before external content reaches host files or extracted transcripts. Ensure resulting files use restrictive permissions and add tests containing representative opaque and recognizable credentials.

## Fix Focus Areas
- internal/runtime/opencode_run.go[275-283]
- internal/runtime/opencode_transcript.go[54-67]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Critical command changes go untested 📘 Rule violation ▣ Testability
Description
The PR modifies internal/cli but its testing declaration explicitly says make e2e-test was not
run. Because these command paths trigger the live installation-flow requirement, the latest changes
have no successful end-to-end result before merge.
Code

internal/cli/admin.go[630]

+	cmd.Flags().StringVar(&runtimeName, "runtime", "claude", "agent runtime for fullsend run (claude, pi, codex, opencode, dummy or dummy-playback; dummy runtimes are for behaviour tests only)")
Relevance

●●● Strong

The active rule explicitly requires e2e evidence for changed internal/cli paths; the PR states the
suite was not run.

PR-#5615

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062051 applies whenever a changed path starts with internal/cli/ and treats missing,
skipped, or unsuccessful make e2e-test evidence as a violation. This PR changes multiple files in
that directory and explicitly reports that the required suite was not run.

Rule 1062051: Run end-to-end tests for critical internal modules before merge
internal/cli/admin.go[630-630]
internal/cli/runtime_binaries_test.go[49-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Changes under `internal/cli` require a successful `make e2e-test` result, but the PR states that this suite was not run.

## Issue Context
Run the required suite in an environment with the live GitHub pool credentials, record its successful result for the latest commit, and address any failures before merge.

## Fix Focus Areas
- internal/cli/admin.go[630-630]
- internal/cli/runtime_binaries_test.go[49-51]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Repository model choices are ignored 🐞 Bug ≡ Correctness
Description
translateOpenCodeModel consults only the built-in alias map and never uses
RunParams.ModelAliases. When a repository remaps an alias such as sonnet, OpenCode instead runs
the built-in generation while the plan can display the repository's configured target.
Code

internal/runtime/opencode_run.go[R56-59]

+	if id, ok := openCodeModelAliases[model]; ok {
+		model = id
+	}
+	return provider + "/" + model
Relevance

●●● Strong

Ignoring repository model aliases violates established runtime precedence and produces incorrect
model selection.

PR-#6467

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The CLI explicitly loads repository aliases and passes them through RunParams.ModelAliases, but
both OpenCode model translation calls accept only params.Model. Existing runtime code establishes
that repository aliases override built-in mappings before provider normalization.

internal/cli/run.go[1043-1064]
internal/cli/run.go[2216-2233]
internal/runtime/opencode_run.go[41-60]
internal/runtime/pi_run.go[143-199]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
OpenCode ignores `.fullsend/config.yaml` model alias overrides even though the runner passes them in `RunParams.ModelAliases`. Merge repository aliases over the built-in OpenCode alias table before constructing the provider/model value.

## Issue Context
Alias resolution must occur before provider-prefix handling because an alias target may already be a complete provider/model reference. Keep command selection, telemetry, and displayed model resolution consistent.

## Fix Focus Areas
- internal/runtime/opencode_run.go[35-60]
- internal/runtime/opencode_run.go[267-300]
- internal/cli/run.go[1043-1064]
- internal/cli/run.go[2216-2233]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (4)
11. Agent model pins are ignored 🐞 Bug ≡ Correctness
Description
buildOpenCodeRunCommand always supplies --model, and translateOpenCodeModel converts an empty
runner model into the default opus. When only the agent definition has a model: value, that
generated frontmatter is overridden and OpenCode runs the default model instead.
Code

internal/runtime/opencode_run.go[R154-158]

+	// translateOpenCodeModel never returns empty (it falls back to the default
+	// alias), so --model is always supplied; opencode's own resolution is a
+	// backstop, not the primary path. --model takes provider/model
+	// (`opencode run --help`).
+	invocation = append(invocation, "--model "+shellQuote(openCodeValidatedArg(modelSpec)))
Relevance

●●● Strong

Always passing a fallback model overrides agent frontmatter, contradicting shared runtime
model-resolution behavior.

PR-#6467

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Bootstrap records def.Model in the translated agent, but command construction always adds a model
after replacing an empty input with opus. The shared runtime model helper states that an agent
definition's frontmatter model is the fallback when the runner resolved none, and Pi applies that
helper during execution.

internal/runtime/opencode_bootstrap.go[147-176]
internal/runtime/opencode_run.go[44-59]
internal/runtime/model.go[8-24]
internal/runtime/pi_run.go[805-819]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
OpenCode unconditionally passes a default `--model`, overriding the model copied from the agent definition into OpenCode frontmatter. Preserve the shared model precedence by using the agent-definition model when the runner did not resolve one, or omit `--model` so OpenCode can use its agent configuration.

## Issue Context
The repository already defines `EffectiveModel(runModel, agentModel)` as the shared fallback chain. Ensure the same effective value drives the command, initialization event, and metrics.

## Fix Focus Areas
- internal/runtime/opencode_bootstrap.go[147-176]
- internal/runtime/opencode_run.go[41-60]
- internal/runtime/opencode_run.go[154-165]
- internal/runtime/model.go[8-24]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Broken streams can pass as successful 🐞 Bug ☼ Reliability
Description
OpenCodeRuntime.Run logs and discards parseOpenCodeStream errors, while its success override
requires a non-nil lastResult. If reading the stream fails and the command reports exit zero, the
run returns success without a result and loses result-derived turn, cost, and token metrics.
Code

internal/runtime/opencode_run.go[R323-327]

+	if _, parseErr := parseOpenCodeStream(reader, handler); parseErr != nil {
+		fmt.Fprintf(os.Stderr, "  progress parser: %v\n", sanitizeOutput(parseErr.Error()))
+		cancel()
+		io.Copy(io.Discard, reader)
+	}
Relevance

●●● Strong

Discarded parser failures causing incomplete metrics match accepted runtime stream-accounting fixes.

PR-#6147
PR-#1982

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
A non-EOF reader error returns before OpenCode's synthesized ResultEvent. Run then cancels and
drains the stream but ignores the parser error in its final decision, and only copies aggregate
metrics when a ResultEvent was received.

internal/runtime/opencode_progress.go[131-144]
internal/runtime/opencode_progress.go[237-256]
internal/runtime/opencode_run.go[302-349]
internal/runtime/pi_progress.go[252-258]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
OpenCode stream read failures are logged but do not affect the returned execution result. Preserve partial metrics where possible and return a failed result or error whenever parsing terminates abnormally without a trustworthy terminal result.

## Issue Context
The parser synthesizes its result only after normal EOF, while `Run` copies metrics only from that result. Pi's parser provides a useful precedent by emitting an incomplete error result even when reading fails.

## Fix Focus Areas
- internal/runtime/opencode_progress.go[131-144]
- internal/runtime/opencode_progress.go[237-256]
- internal/runtime/opencode_run.go[302-349]
- internal/runtime/pi_progress.go[252-258]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Sub-agent dispatch silently disappears 🐞 Bug ≡ Correctness
Description
openCodeToolForClaude maps the legacy Task name but omits the current Agent alias used by
Claude-style definitions. An agent declaring tools: Agent has that entry dropped as unsupported,
so OpenCode never receives its task tool permission.
Code

internal/runtime/opencode_bootstrap.go[R225-228]

+	"LS":        "list",
+	"WebFetch":  "webfetch",
+	"Task":      "task",
+}
Relevance

●●● Strong

The current tool translation omits a supported Claude alias, silently removing sub-agent capability.

PR-#6752

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
OpenCode drops names absent from its mapping, and the added map contains only Task: task. The
existing Pi translation explicitly recognizes both Agent and Task as equivalent sub-agent tools,
demonstrating the supported Claude-style vocabulary.

internal/runtime/opencode_bootstrap.go[184-200]
internal/runtime/opencode_bootstrap.go[217-228]
internal/runtime/pi_agent.go[213-234]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The OpenCode tool translator recognizes only the legacy `Task` spelling for sub-agent dispatch. Map both `Agent` and `Task` to OpenCode's `task` tool and add coverage for the modern spelling.

## Issue Context
The shared Claude-style parser and Pi translation treat `Agent` and `Task` as aliases. OpenCode currently drops every unknown tool with a warning, turning this omission into a disabled capability.

## Fix Focus Areas
- internal/runtime/opencode_bootstrap.go[179-228]
- internal/runtime/opencode_test.go[77-91]
- internal/runtime/pi_agent.go[213-234]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


14. Missing transcripts appear successful 🐞 Bug ☼ Reliability
Description
The generated pipeline records and returns only OpenCode's exit status after piping stdout through
tee, discarding a failed tee status. When the sandbox cannot create or write the transcript
file, the host can still parse stdout and report a successful run, while extraction merely logs that
no transcript was found.
Code

internal/runtime/opencode_run.go[R199-201]

+	pipeline := "{ " + strings.Join(invocation, " ") + " ; echo $? > " + shellQuote(rcFile) + " ; }" +
+		" | tee " + shellQuote(sandboxTranscript) +
+		" ; exit \"$(cat " + shellQuote(rcFile) + " 2>/dev/null || echo 1)\""
Relevance

●●● Strong

Specific pipeline error propagation bug can falsely report success; reliability fixes in OpenCode
are accepted.

PR-#6147

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The pipeline saves only the left command's status in the rc file and exits with it after tee, so a
tee write failure cannot affect the returned status. Transcript extraction relies solely on the
sandbox file and treats an absent file as a non-fatal informational condition.

internal/runtime/opencode_run.go[185-203]
internal/runtime/opencode_transcript.go[41-67]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A failed sandbox-side `tee` is ignored, allowing successful runs without the transcript artifact that this runtime promises to extract.

## Issue Context
The command must preserve OpenCode's exit code across the pipeline, but it also needs to detect a non-zero `tee` result before returning success.

## Fix Focus Areas
- internal/runtime/opencode_run.go[185-203]
- internal/runtime/opencode_transcript.go[41-67]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 75 rules
✅ Cross-repo context — repo relationships
  Explored: repo: fullsend-ai/agents (sha: ec2f8a5b)
  Explored: repo: fullsend-ai/.fullsend (sha: 7c163cad)
Review mode: 🧠 Deep: This adds a security-sensitive, opt-in runtime with substantial new bootstrap, shell execution, permissions, model translation, transcript, and configuration logic across many independent paths.

Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +281 to +283
defer f.Close()
reader = io.TeeReader(stdout, f)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Run artifacts can expose credentials 📘 Rule violation ⛨ Security

Run tees the raw OpenCode event stream directly into OutputPath, and ExtractTranscripts
downloads the second raw copy without applying credential-literal or secret-pattern redaction. When
model or tool output contains a token, key, password, or credential-bearing command output, both
persisted artifacts receive it unchanged.
Agent Prompt
## Issue description
OpenCode output and transcript artifacts are persisted without the repository's required credential-redaction passes.

## Issue Context
Apply sensitive runner-environment literal replacement and `security.SecretRedactor` before external content reaches host files or extracted transcripts. Ensure resulting files use restrictive permissions and add tests containing representative opaque and recognizable credentials.

## Fix Focus Areas
- internal/runtime/opencode_run.go[275-283]
- internal/runtime/opencode_transcript.go[54-67]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread docs/runtimes/opencode.md
Comment on lines +14 to +17
> **Experimental — read-only agents only.** OpenCode is enabled for read-only agents (`triage`,
> `prioritize`). Write-capable agents (`code`, `fix`) are gated on the security-hook adapter, tracked
> in [unbound-force#515](https://github.com/unbound-force/unbound-force/issues/515): OpenCode has no
> native PreToolUse/PostToolUse hooks, so until the runner-owned, sha256-gated plugin adapter lands,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Readers mistake deferred work as ready 📜 Skill insight ≡ Correctness

docs/runtimes/opencode.md presents the unimplemented security-hook adapter under an Experimental
blockquote rather than the required > **Planned:** callout. The same deferred adapter governs
write-path denial and sandbox hooks, so readers encounter future behavior in several capability
descriptions without the standardized status marker.
Agent Prompt
## Issue description
Documentation mentions the unimplemented OpenCode hook adapter without the required planned-feature callout format.

## Issue Context
Convert each mention of deferred adapter, write-denial, hook, or transcript work into a `> **Planned:**` blockquote containing the relevant linked issue, while keeping current capabilities distinct from future ones.

## Fix Focus Areas
- docs/runtimes/opencode.md[14-18]
- docs/runtimes/opencode.md[41-44]
- docs/runtimes/opencode.md[96-98]
- docs/contributing/runtime-implementation.md[157-175]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread internal/cli/admin.go
cmd.Flags().BoolVar(&publicApps, "public", false, "create public (unlisted) GitHub Apps installable by other orgs")
cmd.Flags().StringVar(&appSet, "app-set", appsetup.DefaultAppSet, "app set name prefix for GitHub Apps (e.g., myorg creates myorg-fullsend, myorg-coder)")
cmd.Flags().StringVar(&runtimeName, "runtime", "claude", "agent runtime for fullsend run (claude, pi or dummy; dummy is for behaviour test orgs only)")
cmd.Flags().StringVar(&runtimeName, "runtime", "claude", "agent runtime for fullsend run (claude, pi, codex, opencode, dummy or dummy-playback; dummy runtimes are for behaviour tests only)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Critical command changes go untested 📘 Rule violation ▣ Testability

The PR modifies internal/cli but its testing declaration explicitly says make e2e-test was not
run. Because these command paths trigger the live installation-flow requirement, the latest changes
have no successful end-to-end result before merge.
Agent Prompt
## Issue description
Changes under `internal/cli` require a successful `make e2e-test` result, but the PR states that this suite was not run.

## Issue Context
Run the required suite in an environment with the live GitHub pool credentials, record its successful result for the latest commit, and address any failures before merge.

## Fix Focus Areas
- internal/cli/admin.go[630-630]
- internal/cli/runtime_binaries_test.go[49-51]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +56 to +59
if id, ok := openCodeModelAliases[model]; ok {
model = id
}
return provider + "/" + model

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Repository model choices are ignored 🐞 Bug ≡ Correctness

translateOpenCodeModel consults only the built-in alias map and never uses
RunParams.ModelAliases. When a repository remaps an alias such as sonnet, OpenCode instead runs
the built-in generation while the plan can display the repository's configured target.
Agent Prompt
## Issue description
OpenCode ignores `.fullsend/config.yaml` model alias overrides even though the runner passes them in `RunParams.ModelAliases`. Merge repository aliases over the built-in OpenCode alias table before constructing the provider/model value.

## Issue Context
Alias resolution must occur before provider-prefix handling because an alias target may already be a complete provider/model reference. Keep command selection, telemetry, and displayed model resolution consistent.

## Fix Focus Areas
- internal/runtime/opencode_run.go[35-60]
- internal/runtime/opencode_run.go[267-300]
- internal/cli/run.go[1043-1064]
- internal/cli/run.go[2216-2233]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +154 to +158
// translateOpenCodeModel never returns empty (it falls back to the default
// alias), so --model is always supplied; opencode's own resolution is a
// backstop, not the primary path. --model takes provider/model
// (`opencode run --help`).
invocation = append(invocation, "--model "+shellQuote(openCodeValidatedArg(modelSpec)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Agent model pins are ignored 🐞 Bug ≡ Correctness

buildOpenCodeRunCommand always supplies --model, and translateOpenCodeModel converts an empty
runner model into the default opus. When only the agent definition has a model: value, that
generated frontmatter is overridden and OpenCode runs the default model instead.
Agent Prompt
## Issue description
OpenCode unconditionally passes a default `--model`, overriding the model copied from the agent definition into OpenCode frontmatter. Preserve the shared model precedence by using the agent-definition model when the runner did not resolve one, or omit `--model` so OpenCode can use its agent configuration.

## Issue Context
The repository already defines `EffectiveModel(runModel, agentModel)` as the shared fallback chain. Ensure the same effective value drives the command, initialization event, and metrics.

## Fix Focus Areas
- internal/runtime/opencode_bootstrap.go[147-176]
- internal/runtime/opencode_run.go[41-60]
- internal/runtime/opencode_run.go[154-165]
- internal/runtime/model.go[8-24]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +199 to +201
pipeline := "{ " + strings.Join(invocation, " ") + " ; echo $? > " + shellQuote(rcFile) + " ; }" +
" | tee " + shellQuote(sandboxTranscript) +
" ; exit \"$(cat " + shellQuote(rcFile) + " 2>/dev/null || echo 1)\""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

10. Missing transcripts appear successful 🐞 Bug ☼ Reliability

The generated pipeline records and returns only OpenCode's exit status after piping stdout through
tee, discarding a failed tee status. When the sandbox cannot create or write the transcript
file, the host can still parse stdout and report a successful run, while extraction merely logs that
no transcript was found.
Agent Prompt
## Issue description
A failed sandbox-side `tee` is ignored, allowing successful runs without the transcript artifact that this runtime promises to extract.

## Issue Context
The command must preserve OpenCode's exit code across the pipeline, but it also needs to detect a non-zero `tee` result before returning success.

## Fix Focus Areas
- internal/runtime/opencode_run.go[185-203]
- internal/runtime/opencode_transcript.go[41-67]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +72 to +73
"OPENCODE_CONFIG_CONTENT", // Vertex provider + permission denials (merges last)
"GOOGLE_APPLICATION_CREDENTIALS", // WIF credential file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

11. First-party models stay unconfigured 🔗 Cross-repo conflict ≡ Correctness

OpenCodeRuntime.EnvExports references OPENCODE_CONFIG_CONTENT, but neither the runtime bootstrap
nor the agents repository constructs the required anthropic-vertex provider and permission policy.
The first-party harnesses provide only the existing Vertex environment and credentials, so selecting
OpenCode leaves its default model reference without the configuration needed for inference.
Agent Prompt
## Issue description
Generate and export the OpenCode provider and permission configuration instead of assuming first-party harnesses already supply `OPENCODE_CONFIG_CONTENT`.

## Issue Context
The agents repository currently supplies Vertex credentials and Claude-oriented variables only. Prefer constructing runner-owned OpenCode configuration in this repository; otherwise coordinate explicit configuration across every agents harness.

## Fix Focus Areas
- internal/runtime/opencode.go[53-74]
- internal/runtime/opencode_bootstrap.go[96-139]
- harness/triage.yaml[23-31]
- env/gcp-vertex.env[1-5]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +248 to +250
stdout, stderr, exitCode, err := sandbox.Exec(sandboxName, "opencode --version", 30*time.Second)
if err != nil {
return fmt.Errorf("opencode preflight: %w", err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

12. First-party images lack opencode 🔗 Cross-repo conflict ☼ Reliability

openCodePreflightVersion now requires opencode --version to succeed, but the sandbox image
source installs the other runtimes without installing OpenCode. The agents repository pins that
image family by digest in its triage and prioritize harnesses, so either advertised first-party
OpenCode pilot fails during bootstrap before an iteration begins.
Agent Prompt
## Issue description
Install a reviewed, pinned OpenCode CLI version in the sandbox images before making the runtime selectable.

## Issue Context
After publishing updated images, coordinate updates to the image digests used by the first-party triage and prioritize harnesses.

## Fix Focus Areas
- images/sandbox/Containerfile[49-104]
- internal/runtime/opencode_bootstrap.go[243-256]
- harness/triage.yaml[11-18]
- harness/prioritize.yaml[11-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +25 to +26
- "**/opencode"
- "**/opencode.exe"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

13. Vertex blocks first-party inference 🔗 Cross-repo conflict ≡ Correctness

The scaffold Vertex profile adds OpenCode binary patterns, but the agents repository's profile
actually selected by first-party harnesses still permits only Claude, pi, and Node. Triage and
prioritize therefore reach the sandbox with a profile that denies OpenCode's Vertex requests even
after an image and provider configuration are supplied.
Agent Prompt
## Issue description
Add the OpenCode executable patterns to the Vertex profile enforced by the agents repository.

## Issue Context
The scaffold copy and fleet copy are release-coupled. Update and validate both before advertising OpenCode for first-party agents.

## Fix Focus Areas
- internal/scaffold/fullsend-repo/profiles/fullsend-vertex-ai.yaml[21-27]
- profiles/fullsend-vertex-ai.yaml[28-32]
- harness/triage.yaml[14-18]
- harness/prioritize.yaml[14-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread internal/config/config.go
// are for behaviour tests only.
func ValidRuntimes() []string {
return []string{"claude", "pi", "codex", "dummy", "dummy-playback"}
return []string{"claude", "pi", "codex", "opencode", "dummy", "dummy-playback"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

14. Write agents run without safeguards 🔗 Cross-repo conflict ⛨ Security

Adding opencode to ValidRuntimes makes it selectable through repository, per-agent, and
command-line overrides, while ResolveForAgent checks neither the agent role nor ReadonlyRepo,
even though OpenCode installs no sandbox hook adapter and enables Write, Edit, and Bash. When
selected for the first-party code or fix agents, or any unrestricted custom agent in a normally
writable harness, execution reaches write-capable tools without the intended read-only rollout gate.
Agent Prompt
## Issue description
Reject OpenCode for write-capable or unrestricted agents until its sandbox hook adapter is installed and integrity-checked. Ensure repository configuration, per-agent configuration, and command-line runtime overrides all enforce the documented read-only limitation before sandbox execution.

## Issue Context
Configuration validation alone cannot identify whether an agent is safe for the OpenCode pilot. Runtime resolution currently accepts OpenCode for any named agent, `ReadonlyRepo` is an independent harness setting that defaults to false, and the OpenCode bootstrap enables write, edit, and bash tools while intentionally installing no hook adapter. Enforce eligibility at a central boundary where the resolved agent and harness capabilities are available, including the intended triage/prioritize or explicitly read-only cases, and add coverage for the first-party `code` and `fix` agents as well as unrestricted custom agents across every runtime-selection path.

## Fix Focus Areas
- internal/config/config.go[317-323]
- internal/runtime/registry.go[69-96]
- internal/cli/run.go[1011-1035]
- internal/cli/run.go[1972-1990]
- internal/runtime/opencode_bootstrap.go[60-66]
- internal/runtime/opencode_bootstrap.go[179-228]
- agents/code.md[2-17]
- agents/fix.md[2-17]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review sweep — 4 findings below (2 HIGH, 2 MEDIUM), verified against upstream OpenCode source and cross-checked against existing bot comments on this PR for duplicates.

// the harness (env.sandbox / host_files); they are listed here so the run
// command can assert they are present and so docs/runtimes.md's config-key
// table stays in sync.
func (r OpenCodeRuntime) EnvExports() []string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Runner never sets OPENCODE_DISABLE_PROJECT_CONFIG; a target repo's own .opencode/opencode.json can re-grant write permissions, and the PR's own doc comment misdescribes this.

Verified against anomalyco/opencode@dev (packages/opencode/src/config/paths.ts directories()): unless OPENCODE_DISABLE_PROJECT_CONFIG is set, OpenCode walks up from the target repo's checkout directory looking for a .opencode/ dir and loads its opencode.json/opencode.jsonc. This PR's diff never sets that flag anywhere (grepped the whole tree). Verified in config.ts (lines ~430-490): every directory's opencode.json (including a repo-authored one, if the walk isn't disabled) is merged into the result via mergeConfigConcatArrays -> remeda mergeDeep (a per-key deep merge, not a replace) BEFORE OPENCODE_CONFIG_CONTENT is merged in last. Because a deep merge only overrides keys the later source (OPENCODE_CONFIG_CONTENT) actually specifies, any write-permission key the runner-owned policy doesn't explicitly touch that a hostile target repo's own .opencode/opencode.json sets to "allow" is not guaranteed to be overridden. This also directly contradicts this PR's own doc comment at opencode.go:18-21, which states OpenCode's "config search path is driven by an explicit OPENCODE_CONFIG_DIR pointer to the runner-owned dir rather than a working-directory scan, so the agent-writable workspace is never consulted" — the upstream source shows the opposite is true by default.

Suggestion: Set OPENCODE_DISABLE_PROJECT_CONFIG=true in EnvExports as defense in depth, and correct the opencode.go:18-21 doc comment's claim that the workspace is never consulted. Separately, whatever builds OPENCODE_CONFIG_CONTENT (in fullsend-ai/agents) must explicitly set every write-capable permission key to "deny" rather than relying on omission.

"MultiEdit": "edit",
"Grep": "grep",
"Glob": "glob",
"LS": "list",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Claude's LS tool is mapped to "list", a tool ID that does not exist anywhere in OpenCode.

openCodeToolForClaude maps "LS": "list" (this line), and the same wrong ID is asserted as fact in opencode.go:59 ("the tools a read-only agent needs (read, grep, glob, list, and read-only bash)"). I enumerated the actual OpenCode tool registry from anomalyco/opencode@dev (packages/opencode/src/tool/registry.ts, cross-checked via gh api file fetch): the full builtin tool-id set is invalid, shell ("bash"), read, glob, grep, edit, write, task, webfetch, todo, search, skill, patch, question, lsp, plan (plus a conditional execute) — there is no "list" tool anywhere in the codebase. Because openCodeToolForClaude["LS"] resolves successfully (to the bogus "list"), the intended warn-and-drop path (openCodeToolsRecord's if !ok branch, opencode_bootstrap.go:196-198) never fires, so the failure is silent. Codified into the merged test suite: TestOpenCodeToolsRecord (opencode_test.go:83-84) asserts []string{"bash", "edit", "glob", "list", "read", "task"} as the expected output for an LS-containing input.

Suggestion: Remove the "LS": "list" entry (or map it to a real equivalent, e.g. glob) and correct the opencode.go:59 comment and TestOpenCodeToolsRecord accordingly. Consider validating openCodeToolForClaude's values against a generated OpenCode tool-ID list in a test.

Description string `json:"description,omitempty"`
Mode string `json:"mode"`
Model string `json:"model,omitempty"`
Tools map[string]bool `json:"tools,omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Bootstrap emits OpenCode's deprecated agent tools: field instead of the current permission: field.

openCodeAgentFrontmatter (opencode_bootstrap.go:141-152) and openCodeAgentMarkdown (opencode_bootstrap.go:154-177) emit the per-agent tools: {toolID: bool} field. Verified against OpenCode's current agent schema (packages/core/src/v1/config/agent.ts): tools is annotated @deprecated Use 'permission' field instead, and the schema's own normalize() transform (same file) converts tools entries into permission entries at load time. The field still works today via that compatibility shim, but new integration code is standardizing on permission: and upstream has already flagged tools: for removal.

Suggestion: Emit the permission: field directly (allow/deny per tool, mirroring what upstream's normalize() does) instead of the deprecated tools: field, to avoid depending on a shim upstream may remove.


// ClearIterationArtifacts removes the previous iteration's outputs and the
// debug log so transcripts and output files are per-iteration.
func (r OpenCodeRuntime) ClearIterationArtifacts(sandboxName string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] OpenCodeRuntime.ClearIterationArtifacts skips the stray-process sweep every other runtime performs.

Every other runtime's ClearIterationArtifacts (ClaudeRuntime, CodexRuntime, PiRuntime, DummyRuntime, DummyPlaybackRuntime — confirmed by grep across the whole internal/runtime package) calls clearStrayProcesses(...) before wiping iteration files. OpenCodeRuntime.ClearIterationArtifacts (opencode_run.go:354-359) only runs rm -rf on the output dir and debug log; it never calls clearStrayProcesses. A timed-out or leftover opencode/child bash process from the previous iteration is not swept, unlike every other runtime's contract.

Suggestion: Call clearStrayProcesses(sandbox.Exec, sandboxName, os.Stderr, "the previous iteration") in OpenCodeRuntime.ClearIterationArtifacts before removing the iteration files, matching the other runtimes.

Bootstrap/Run/ExtractTranscripts/ConfigDir for the OpenCode agent runtime,
mirroring the pi runtime pattern. Flip config.ValidRuntimes() to include
"opencode" so org/per-repo/per-agent config can select it.

What lands in this PR
- sandbox.SandboxOpenCodeConfig (/sandbox/opencode-config) — runner-owned
  config dir, off the agent-writable workspace. Path convention pinned by
  unbound-force#515 (hook adapter also lives here under plugins/).
- opencode.go: ConfigDir → SandboxOpenCodeConfig; EnvExports exports
  OPENCODE_CONFIG_DIR (runner-owned dir pointer), OPENCODE_CONFIG_CONTENT
  (Vertex provider + permission-deny config, merges last in opencode's config
  stack so it wins over any repo config), and GOOGLE_APPLICATION_CREDENTIALS;
  implements DebugLogNamer.
- opencode_bootstrap.go: Bootstrap() creates agent/, skills/, plugins/ under
  ConfigDir; translates the Claude-style agent .md to OpenCode's
  {agent,agents}/**/*.md layout with JSON frontmatter (mode: primary, tools
  as {toolID: bool}); uploads harness skills; preflights opencode --version.
  No ClaudeHooksBootstrap — hook adapter is unbound-force#515; plugin path
  reserved at ConfigDir/plugins/fullsend-hooks.ts with sha256 fail-closed
  guard shape (exit 97, before .env) for fullsend-ai#515 to populate.
- opencode_run.go: buildOpenCodeRunCommand renders "opencode run --format json
  --model <provider/model> --variant <effort> --agent <name> <prompt>
  </dev/null"; honors RunParams.Prompt (feedback_mode); shell-escapes all
  interpolated values (sh -c boundary); emits InitEvent from RunParams.Model
  (wire format carries no model metadata); captures ResultEvent metrics;
  overrides exit 0 when the stream reports an error (same as pi).
- opencode_transcript.go: ExtractTranscripts downloads the tee'd output.jsonl
  (interim approach; full redesign → unbound-force#513); ParseTranscriptFile
  replays ndjson through parseOpenCodeStream for the exit-0 override;
  ExtractDebugLog downloads opencode-debug.log.
- config.ValidRuntimes(): adds "opencode"; all call sites that hardcoded
  "opencode" as a rejected stub updated to accept it or use "nonexistent"
  for the invalid-name coverage.
- docs/contributing/runtime-implementation.md: security matrix OpenCode
  column filled in (hooks: not wired / fullsend-ai#515; transcripts: interim tee;
  host-side scans: runtime-agnostic). docs/runtimes.md: config-key table
  OpenCode column added; runtime table entry updated from "Stub" to
  "Implemented".
- 44 new tests; patch coverage on new opencode production files ≈ 82%.

What does NOT land (owned by unbound-force#515)
- Hook plugin adapter (tool.execute.before/after) — path reserved.
- SandboxHooksBootstrap type-assert in Bootstrap.
- ContextBridger: omitted — opencode reads AGENTS.md natively.

Phase 0 (opencode headless in sandbox) confirmed via unbound-force#509.
ConfigDir path + sha256 convention pinned per unbound-force#515 comment.

Signed-off-by: Yvonne Devlin <ydevlin@redhat.com>
Signed-off-by: Yvonne Devlin <ydevlin@redhat.com>
@yvonnedevlinrh
yvonnedevlinrh force-pushed the feat/opencode-runtime-510 branch from 06db3c2 to 236d183 Compare September 8, 2026 15:24
Comment thread docs/runtimes/opencode.md
| Unattended | No approval prompts, stdin closed; a non-config-allowed tool request is auto-rejected |
| Artifacts | `output.jsonl`, `transcripts/<agent>-output.jsonl`, `metrics.json` with `runtime: opencode`, plus `opencode-debug.log` with `--debug` |
| Extra knobs | `FULLSEND_OPENCODE_PROVIDER` (prefix for bare ids) |
| Not supported | Fallback chains, `plugins:` (Claude marketplace layout), sandbox tool hooks (until #515) |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we started to introduce a plugins key that depends on Runtime implementation, so even it is called plugins it could be applied to OpenCode. The code would place the plugins on the correct place for them to work. Would this solve the "not supported"? I don't recall if it was already merged or not.

@rh-hemartin rh-hemartin added the ok-to-test Allow e2e CI to run after maintainer review (must be re-applied after each push) label Sep 9, 2026
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.06250% with 67 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/runtime/opencode_bootstrap.go 75.25% 12 Missing and 12 partials ⚠️
internal/runtime/opencode_run.go 82.73% 14 Missing and 10 partials ⚠️
internal/runtime/opencode_transcript.go 72.05% 11 Missing and 8 partials ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Site preview

Preview: https://e13e2f7d-site.fullsend-ai.workers.dev

Commit: 236d183d60ce50a440562289fc97b67cbeb80694

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review sweep — 7 findings (2 HIGH, 5 MEDIUM), each verified against this PR's head, the sibling runtime implementations, and upstream OpenCode source. Cross-checked against the existing threads on this PR; the two that sit near an existing thread carry an explicit note on how they differ.

Six findings are inline below. One has no line inside a diff hunk, so it is recorded here:


[MEDIUM] Two consumers this PR's own runtime-implementation checklist marks required were not updated

docs/contributing/runtime-implementation.md (checklist lines 83 and 90 — outside this PR's diff hunks, hence this body comment)

The "Consumer-completeness touchpoints" checklist that this PR itself edits lists at line 83 [ ] docs/architecture.md — update the runtime selection diagram and at line 90 [ ] docs/guides/infrastructure/layered-config-reference.md — update.

Neither file appears in this PR's changed-file list, and both still exclude opencode on the head branch:

  • docs/architecture.md:198 renders the config node as runtime: claude | pi | codex | dummy | dummy-playback
  • docs/guides/infrastructure/layered-config-reference.md:137 reads Valid values: claude, pi, codex, dummy, ...

The PR did update the other consumers on the list (choosing-a-runtime.md, cli/run.md, runtimes.md), so this reads as two specific misses rather than a wholesale omission. A user following layered-config-reference.md will conclude that runtime: opencode is invalid.

Suggestion: Add opencode to the docs/architecture.md:198 runtime selection diagram (and the surrounding prose at line 232) and to the valid-values list at layered-config-reference.md:137, then tick both checklist boxes.

// An agent that listed only unsupported/Skill tools gets an explicit
// empty record rather than nil, so OpenCode does not silently fall
// back to its full default tool set.
return map[string]bool{}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] openCodeToolsRecord can only widen permissions; omitempty drops the deliberate empty record, and Bash(a,b,c) collapses to a bare bash: true

The translated tool record is this PR's only code-level tool restriction, and as written it cannot restrict anything. Three separate mechanisms:

  1. The deliberate empty record is silently dropped. Lines 202-206 return map[string]bool{} with the comment that a Skill-only agent "gets an explicit empty record rather than nil, so OpenCode does not silently fall back to its full default tool set" — but the field is declared at line 151 as a map[string]bool carrying the JSON tag tools,omitempty, and Go's omitempty omits an empty map. Compiling and marshalling the exact struct shape confirms it: json.Marshal(fm{Mode: "primary", Tools: map[string]bool{}}) emits {"mode":"primary"} with no tools key at all, so OpenCode applies its full default set (write, edit, bash, webfetch) — precisely what the comment promises to prevent.
  2. The record only ever emits true. Line 205 is rec[ot] = true and there is no deny path anywhere in the function, so a tool that is absent from the record keeps OpenCode's default rather than being denied.
  3. The Bash sub-command allowlist is parsed and then discarded, with no warning. parseClaudeToolSpecs populates def.BashAllowlist (pi_agent.go:88, documented at pi_agent.go:25-27), but grep -rn BashAllowlist internal/runtime/ returns zero hits in any opencode_*.go file. So an agent declaring tools: Bash(gh,curl,jq) collapses to an unrestricted bash: true. Codex at least surfaces the gap (codex_bootstrap.go:196-200: "Agent Bash allowlist (%s) is recorded but not enforced on codex"); OpenCode is silent about it.

Suggestion: Emit OpenCode's permission: map instead of tools:, with an explicit deny for every tool not in the Claude allowlist, and translate Bash(a,b,c) into a bash pattern map ({"gh *": "allow", ..., "*": "deny"}). Drop omitempty from the Tools/Permission field so a deliberately empty record still serializes. Add tests asserting (a) a Skill-only agent serializes a restrictive record and (b) Bash(gh,jq) produces a deny-by-default pattern map.

return sandbox.SandboxWorkspace + "/" + openCodeOutputSubdir + "/" + openCodeOutputFile
}

// buildOpenCodeRunCommand renders the in-sandbox command line for one

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] The prelude sources the agent-writable .env but never re-pins OPENCODE_CONFIG_DIR / OPENCODE_CONFIG_CONTENT, unlike pi and codex

buildOpenCodeRunCommand's prelude (lines 96-99) is && . <envFile> followed only by && export FULLSEND_RUNTIME=opencode. Nothing re-applies r.EnvExports() afterwards.

Both sibling runtimes do, with comments naming this exact threat:

  • pi_run.go:394-398 — ".env is agent-writable; re-pin the runner-owned locations and the offline switches after it so a rewritten .env cannot move pi's config dir out from under the guards below"
  • codex_run.go:313 — ".env is agent-writable; re-pin the runner-owned config location after it so a rewritten .env cannot move codex's home out from under the guards"

OpenCode is the runtime where this matters most: per this PR's own design, the entire provider definition and permission policy travel in OPENCODE_CONFIG_CONTENT, and the config search is pinned by OPENCODE_CONFIG_DIR — both delivered through .env. An agent that has bash in iteration N can rewrite the workspace .env so that iteration N+1 points OPENCODE_CONFIG_DIR at a workspace-writable directory, or replaces the permission policy outright.

Suggestion: After . .env in the prelude, append && + strings.Join(r.EnvExports(), " && "), mirroring pi_run.go:398 and codex_run.go:313, and add a buildOpenCodeRunCommand test asserting the config-dir export appears after the .env source.

// effort, e.g., high, max, minimal)"). Verified against opencode CLI.
invocation = append(invocation, "--variant "+shellQuote(openCodeValidatedArg(params.Effort)))
}
invocation = append(invocation, "--agent "+shellQuote(openCodeValidatedArg(agentName)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] An --agent miss silently falls back to OpenCode's default agent with the full default tool set, and the runner never detects it

(Distinct from the --model override thread just above — this one is about --agent resolution, not model selection.)

Verified against upstream packages/opencode/src/cli/cmd/run.ts: pickAgent (line 661) delegates to localAgent/attachAgent, and on every miss those print a warning via UI.println and return undefinedagent "${name}" not found. Falling back to default agent (lines 606 and 644) and agent "${name}" is a subagent, not a primary agent. Falling back to default agent (lines 614 and 653). Returning undefined means opencode proceeds under its default primary agent with the default tool set, so the entire translated permission record is bypassed and the run still reports success.

The runner has no detection for this: Run only parses the --format json stream and the exit code. Triggers that are live in this PR: the config dir failing to load, a frontmatter parse failure on the translated agent file, and a name divergence between the two independent sources — Bootstrap writes the file at openCodeAgentPath(input.AgentName()) (opencode_bootstrap.go:38-39, unsanitized), while Run passes --agent openCodeValidatedArg(params.AgentBaseName) (this line), which strips every character outside [A-Za-z0-9-_./@ ]. For the current first-party agent names (triage, prioritize) the strip is a no-op, so the divergence trigger is latent rather than active; the fallback behaviour itself is upstream-confirmed.

Suggestion: Set default_agent to the translated agent name in the runner-owned config so a fallback still lands on the intended agent, and treat opencode's "Falling back to default agent" output as a hard iteration failure. Add a Bootstrap assertion that the name written to agent/<name>.md is byte-identical to the value Run passes to --agent.

// sandbox image's /bin/sh (dash) without relying on the non-POSIX
// `pipefail`. The prelude (guard, .env) runs before the pipeline so its
// own exits — notably the guard's 97 — are not swallowed by the subshell.
pipeline := "{ " + strings.Join(invocation, " ") + " ; echo $? > " + shellQuote(rcFile) + " ; }" +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] A stale .opencode-run-rc plus an unconditional ; exit "$(cat rc)" masks prelude failures with the previous iteration's exit code

(Distinct from the neighbouring thread about tee's discarded exit status — this is about the rc file surviving between iterations and the final exit running even when the prelude short-circuits.)

Lines 199-201 build { opencode ... ; echo $? > rc ; } | tee <transcript> ; exit "$(cat rc 2>/dev/null || echo 1)", and line 164 joins that to the prelude with &&. Because ; binds looser than &&, the final exit runs unconditionally even when the prelude short-circuits. Reproduced under /bin/sh with a pre-existing rc containing 0:

cd . && false && { :; echo $? > .opencode-run-rc; } | tee out ; exit "$(cat .opencode-run-rc || echo 1)"
# exits 0

The rc file survives between iterations: it is written to sandbox.SandboxWorkspace + "/" + openCodeRunRCFile (line 121, const at line 86), while ClearIterationArtifacts (lines 354-357) removes only WorkspaceDir()/output/* and the debug log. So on iteration >= 2, a failed cd, mkdir, or . .env re-raises the previous iteration's status.

The impact is diagnostic masking rather than a false success: the stream is empty, so parseOpenCodeStream's EOF synthesis sets isError = sawError || numTurns == 0 (opencode_progress.go:242) and Run's exitCode == 0 && lastResult.IsError branch converts it to 1 — but the operator then sees "opencode exited 0 but the stream reports an error" instead of the actual prelude failure.

Suggestion: Place the rc file under the output/ subdirectory that ClearIterationArtifacts already wipes (or rm -f it in the prelude), and make the final exit conditional on the prelude having succeeded so a prelude failure propagates its own status.

// keeps opencode's stderr out of the tee'd stdout transcript.
invocation = append(invocation, "</dev/null")
if params.Debug != "" {
invocation = append(invocation, "2>>"+shellQuote(sandbox.SandboxWorkspace+"/"+openCodeDebugLogFile))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] The debug log captures only direct stderr writes; OpenCode's structured logs need --print-logs and never reach it

With params.Debug set, this line appends only 2>><debug log>. Verified in upstream packages/core/src/observability/logging.ts:

export function loggers() {
  return process.env.OPENCODE_PRINT_LOGS === "1" ? [fileLogger(), stderrLogger] : [fileLogger()]
}

The stderr logger is attached only when OPENCODE_PRINT_LOGS is set, which happens solely through the global --print-logs flag (packages/opencode/src/index.ts:53-67, whose middleware also maps --log-level onto OPENCODE_LOG_LEVEL). Neither the flag nor the env var is set anywhere in this PR. So the debug log receives only what opencode writes directly to stderr — uncaught crashes and a few UI warnings — while every structured log line goes to OpenCode's own log file inside the sandbox and is never extracted. docs/runtimes/opencode.md tells operators that sandbox-side failures land in opencode-debug.log, which overstates what will actually be there.

Suggestion: When Debug is set, add the global flags before the run subcommand (opencode --print-logs --log-level DEBUG run ...) and keep the stderr redirect; extend the buildOpenCodeRunCommand debug test to assert their presence and placement.

// (unbound-force#510).
cfg.Defaults.Runtime = "opencode"
require.Error(t, cfg.Validate())
require.NoError(t, cfg.Validate(), "opencode is user-selectable (unbound-force#510)")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] This removes the ADR 0044 org-mode deprecation guard and adds new positive org-mode coverage

AGENTS.md:28 states: "Per-org installation mode is deprecated (ADR 0044) and is being removed. This applies to human contributors and agents alike: do not add or extend org-mode-specific content in docs or code, and when reviewing a PR that touches org-mode content, flag it as referencing deprecated functionality."

This hunk does both things that rule forbids. In TestOrgConfigValidateRuntime it removes the standing guard comment — "No codex case here: org mode is deprecated (ADR 0044), so codex's selectability is asserted on the per-repo and agents: paths instead" — and replaces the negative assertion with a new positive one (cfg.Defaults.Runtime = "opencode" / require.NoError(t, cfg.Validate(), ...)), extending OrgConfig coverage to a newly added runtime. docs/runtimes/opencode.md:3 correspondingly advertises the runtime as "opt-in per org".

The equivalent per-repo assertion at line 695 (TestPerRepoConfigValidate_Runtime) already covers selectability, so the org-mode case adds no coverage the repo's own convention permits.

Suggestion: Restore the "org mode is deprecated (ADR 0044)" comment in TestOrgConfigValidateRuntime and keep opencode selectability asserted only on the per-repo and agents: paths, matching how codex was handled. Reword docs/runtimes/opencode.md:3 to "opt-in per repo".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Allow e2e CI to run after maintainer review (must be re-applied after each push)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants