Skip to content

ci(e2e): add Gherkin behaviour tests with dummy runtime - #1982

Merged
ifireball merged 27 commits into
mainfrom
cursor/5525b289
Jul 8, 2026
Merged

ci(e2e): add Gherkin behaviour tests with dummy runtime#1982
ifireball merged 27 commits into
mainfrom
cursor/5525b289

Conversation

@ifireball

@ifireball ifireball commented Jun 7, 2026

Copy link
Copy Markdown
Member

Summary

  • Add defaults.runtime org config and --runtime install flag with shared runtime.ResolveFromConfig() selection in fullsend run.
  • Implement a dummy runtime that executes scripted sandbox operations and emits behaviour-results.json for deterministic assertions.
  • Introduce godog behaviour tests under e2e/behaviour/ with pluggable GitHub/GitHub Actions drivers, triage scenarios, CI job, and ADR/docs.
  • Per-repo install in CI (BEHAVIOUR_INSTALL_MODE=per-repo): suite runs fullsend inference provision (when E2E_GCP_PROJECT_ID is set) and fullsend github setup --runtime dummy on pool test-repo. Per-repo mint enrollment is a one-time admin step on the hosted mint project — the install driver does not run mint enroll at test time.

Ops / merge prerequisites

Behaviour CI uses the same pool org credentials as admin e2e. Per-repo triage workflows need pool org test-repo repos enrolled once on the hosted mint project (PER_REPO_WIF_REPOS). See e2e-testing.md — Behaviour tests and per-repo mint enrollment.

When E2E_GCP_PROJECT_ID is set, the behaviour install driver runs fullsend inference provision <org>/test-repo using CI credentials on the inference project (same access model as admin e2e). No additional mint-project IAM roles are required on E2E_GCP_SERVICE_ACCOUNT beyond what admin e2e already needs.

Pool recovery: CleanupStaleResources runs before each install (teardown-first). Per-repo teardown removes shim workflow, enrollment branches, and fullsend PRs; a fresh github setup is the canonical reset if .fullsend/ artifacts remain from a partial run.

Test plan

  • go test ./...
  • go test -tags behaviour -c ./e2e/behaviour/...
  • go test -tags e2e -c ./e2e/admin/...
  • make behaviour-test against halfsend org pool with GITHUB_TOKEN and per-repo dummy runtime
  • CI behaviour job green

Closes #2276

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown

Site preview

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

Commit: 0b5732587b5f3fefd25ceef9606adb41d095e42d

@ifireball ifireball self-assigned this Jun 7, 2026
@ifireball ifireball changed the title Add Gherkin behaviour tests with dummy runtime feat(e2e): add Gherkin behaviour tests with dummy runtime Jun 7, 2026
@ifireball
ifireball marked this pull request as ready for review June 7, 2026 11:07
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review

5 findings (0 critical, 0 high, 1 medium, 4 low)

Re-review of files changed since prior review at e967c12. Two new commits update docs (ADR 0003 cross-reference, ADR 0066 refinements, architecture.md, behaviour-testing.md, testing-agents.md) and add cleanup test coverage (cleanup.go, cleanup_test.go). All changes are within the authorized scope of issue #2276. The prior medium finding on dummy.go curl argument ordering remains unfixed on unchanged code (severity anchored). One new low finding on unreachable dead code in provisioner.go.


Findings

Medium

  • [correctness] internal/runtime/dummy.gourl_get curl command places -o /dev/null after --: curl -sf -- %s -o /dev/null. After --, curl treats all remaining arguments as URL operands, so -o and /dev/null are interpreted as URLs rather than the output-redirect option. The response body is not discarded and curl attempts two bogus URL fetches. The current "Sandbox blocks disallowed outbound URL" scenario still passes because the sandbox network policy blocks the request before the argument parsing issue matters, but the test validates the wrong thing if the sandbox ever permits the URL. Unchanged since prior review; severity anchored.
    Remediation: Move -o /dev/null before --: curl -sf -o /dev/null -- %s.

Low

  • [fail-open] internal/runtime/registry.go — The dummy runtime is unconditionally available to any org that sets defaults.runtime: dummy or per-repo runtime: dummy. ADR 0066 documents this as intentional; activation requires deliberate admin configuration via fullsend github setup --runtime dummy. Carried forward from prior review.

  • [stale-reference] docs/ADRs/0040-org-pool-for-parallel-e2e-tests.md — ADR 0040 references outdated pool org range "halfsend-01 through halfsend-06". The PR updates e2e-testing.md to halfsend-01..12 but ADR 0040 was not updated. Carried forward from prior review.

  • [pattern-inconsistency] internal/forge/github/github.gocommitFilesWithRetry adds log.Printf for retry logging. This package has zero log.Printf calls on main and does not import "log" — it relies on error returns for observability. The new import breaks the package's zero-logging pattern. Carried forward from prior review.

  • [dead-code] internal/dispatch/gcf/provisioner.go — In EnsureOrgInMint, the mintURI == "" check after resolveMintURI is unreachable. resolveMintURI calls DiscoverMint, which always returns an error (wrapping ErrFunctionNotFound) when the URI is empty — the function never returns ("", nil). The empty-check branch is dead code.

Previous run

Review

1 finding (0 critical, 0 high, 1 medium) | Verdict: comment

Re-review of files changed since prior review at ddffbe5. Two new commits: 5c4cd4f (address Review Squad findings) and e967c12 (merge main). The substantive changes refactor DummyRuntime from package-level var overrides to struct-field dependency injection, add an aggregate zip extraction limit (100MB), fix AddIssueLabels request body format, harden parseInferenceStatusWIFProvider against leading log noise, and pin godog concurrency to 1. All changes are review-feedback fixes within the authorized scope of issue #2276. No new medium+ issues introduced. The prior medium finding on dummy.go curl argument ordering remains unfixed on unchanged code (severity anchored).


Findings

Medium

  • [correctness] internal/runtime/dummy.gourl_get curl command places -o /dev/null after --: curl -sf -- %s -o /dev/null. After --, curl treats all remaining arguments as URL operands, so -o and /dev/null are interpreted as URLs rather than the output-redirect option. The response body is not discarded and curl attempts two bogus URL fetches. The current "Sandbox blocks disallowed outbound URL" scenario still passes because the sandbox network policy blocks the request before the argument parsing issue matters, but the test validates the wrong thing if the sandbox ever permits the URL. Unchanged since prior review; severity anchored.
    Remediation: Move -o /dev/null before --: curl -sf -o /dev/null -- %s.

Low

  • [fail-open] internal/runtime/registry.go:17 — The dummy runtime is unconditionally available to any org that sets defaults.runtime: dummy. ADR 0065 documents this as intentional; activation requires deliberate admin configuration. Carried forward from prior review.

  • [stale-reference] docs/ADRs/0040-org-pool-for-parallel-e2e-tests.md:36 — ADR 0040 references outdated pool org range "halfsend-01 through halfsend-06". The PR updates e2e-testing.md to halfsend-01..12 but ADR 0040 was not updated. Carried forward from prior review.

  • [style] internal/forge/github/github.go:758commitFilesWithRetry adds log.Printf for retry logging. This package has zero log.Printf calls on main and does not import "log" — it relies on error returns for observability. The new import breaks the package's zero-logging pattern. Carried forward from prior review.

  • [code-duplication] e2e/behaviour/drivers/ci/githubactions/githubactions.go / internal/forge/github/github.go — Two near-identical readLimited / readLimitedBytes functions (read limit+1, check length > limit). Extracting to a shared utility would create a cross-package dependency between e2e tests and production code, so this is a minor DRY observation. Carried forward from prior review.

Previous run (2)

Review

1 finding (0 critical, 0 high, 1 medium) | Verdict: comment

Re-review with no file changes since prior review at 6dc0c32. The only new commit (ddffbe5) is an empty CI re-trigger. All findings carried forward from prior review.


Findings

Medium

  • [correctness] internal/runtime/dummy.gourl_get curl command places -o /dev/null after --: curl -sf -- %s -o /dev/null. After --, curl treats all remaining arguments as URL operands, so -o and /dev/null are interpreted as URLs rather than the output-redirect option. The response body is not discarded and curl attempts two bogus URL fetches. The current "Sandbox blocks disallowed outbound URL" scenario still passes because the sandbox network policy blocks the request before the argument parsing issue matters, but the test validates the wrong thing if the sandbox ever permits the URL. Unchanged since prior review; severity anchored.
    Remediation: Move -o /dev/null before --: curl -sf -o /dev/null -- %s.

Low

  • [fail-open] internal/runtime/registry.go:13 — The dummy runtime is unconditionally available to any org that sets defaults.runtime: dummy. ADR 0065 documents this as intentional; activation requires deliberate admin configuration. Carried forward from prior review.

  • [stale-reference] docs/ADRs/0040-org-pool-for-parallel-e2e-tests.md:36 — ADR 0040 references outdated pool org range "halfsend-01 through halfsend-06". The PR updates e2e-testing.md to halfsend-01..12 but ADR 0040 was not updated. Carried forward from prior review.

  • [style] internal/forge/github/github.gocommitFilesWithRetry adds log.Printf for retry logging. This package has zero log.Printf calls on main and does not import "log" — it relies on error returns for observability. The new import breaks the package's zero-logging pattern. Carried forward from prior review.

  • [code-duplication] e2e/behaviour/drivers/ci/githubactions/githubactions.go / internal/forge/github/github.go — Two near-identical readLimited / readLimitedBytes functions (read limit+1, check length > limit). Extracting to a shared utility would create a cross-package dependency between e2e tests and production code, so this is a minor DRY observation. Carried forward from prior review.

Previous run (3)

Review

1 finding (0 critical, 0 high, 1 medium) | Verdict: comment

Re-review of files changed since prior review at 7e6d438. New incremental changes add DownloadNamedArtifactFromRun to the CI driver, refactor extractArtifactZip to use temp files with proper error-returning path traversal rejection, add poll deduplication via newestRepositoryArtifactCreatedAt, extract shared scm.ParseRepo, and add runtime selection source logging in run.go. The prior medium finding on dummy.go curl argument ordering remains unfixed on unchanged code (severity anchored). Security review found no new issues — path traversal checks are correct, validateHTTPURL is intact, and the runtime logging does not leak sensitive data.


Findings

Medium

  • [correctness] internal/runtime/dummy.gourl_get curl command places -o /dev/null after --: curl -sf -- %s -o /dev/null. After --, curl treats all remaining arguments as URL operands, so -o and /dev/null are interpreted as URLs rather than the output-redirect option. The response body is not discarded and curl attempts two bogus URL fetches. The current "Sandbox blocks disallowed outbound URL" scenario still passes because the sandbox network policy blocks the request before the argument parsing issue matters, but the test validates the wrong thing if the sandbox ever permits the URL. Unchanged since prior review; severity anchored.
    Remediation: Move -o /dev/null before --: curl -sf -o /dev/null -- %s.

Low

  • [fail-open] internal/runtime/registry.go:13 — The dummy runtime is unconditionally available to any org that sets defaults.runtime: dummy. ADR 0065 documents this as intentional; activation requires deliberate admin configuration. Carried forward from prior review.

  • [stale-reference] docs/ADRs/0040-org-pool-for-parallel-e2e-tests.md:36 — ADR 0040 references outdated pool org range "halfsend-01 through halfsend-06". The PR updates e2e-testing.md to halfsend-01..12 but ADR 0040 was not updated. Carried forward from prior review.

  • [style] internal/forge/github/github.gocommitFilesWithRetry adds log.Printf for retry logging. This package has zero log.Printf calls on main and does not import "log" — it relies on error returns for observability. The new import breaks the package's zero-logging pattern. Carried forward from prior review.

  • [code-duplication] e2e/behaviour/drivers/ci/githubactions/githubactions.go / internal/forge/github/github.go — Two near-identical readLimited / readLimitedBytes functions (read limit+1, check length > limit). Extracting to a shared utility would create a cross-package dependency between e2e tests and production code, so this is a minor DRY observation. Carried forward from prior review.

Previous run (4)

Review

1 finding (0 critical, 0 high, 1 medium) | Verdict: comment

Re-review of files changed since prior review at 69a35e0. Both prior medium findings are resolved: OrgConfigFromPerRepo now copies Runtime ([consumer-completeness]), and url_get validates URL scheme via validateHTTPURL ([injection-vuln]). Commit 2dc14f6 also hardens extractArtifactZip (name sanitization, symlink rejection, proper limit enforcement), adds sentinel errors with errors.Is() matching, propagates context cancellation through executeBehaviourScript, and fixes lock release ordering in the behaviour suite. One new medium finding in the URL validation fix.


Findings

Medium

  • [correctness] internal/runtime/dummy.gourl_get curl command places -o /dev/null after --: curl -sf -- %s -o /dev/null. After --, curl treats all remaining arguments as URL operands, so -o and /dev/null are interpreted as URLs rather than the output-redirect option. The response body is not discarded and curl may return exit code 3 (URL malformat) for the spurious -o operand. The current "Sandbox blocks disallowed outbound URL" scenario still passes because the sandbox network policy blocks the request before the argument parsing issue matters, but the test validates the wrong thing if the sandbox ever permits the URL — it would fail due to curl syntax, not network policy.
    Remediation: Move -o /dev/null before --: curl -sf -o /dev/null -- %s.

Low

  • [fail-open] internal/runtime/registry.go:13 — The dummy runtime is unconditionally available to any org that sets defaults.runtime: dummy. ADR 0065 documents this as intentional; activation requires deliberate admin configuration. Carried forward from prior review.

  • [stale-reference] docs/ADRs/0040-org-pool-for-parallel-e2e-tests.md:36 — ADR 0040 references outdated pool org range "halfsend-01 through halfsend-06". The PR updates e2e-testing.md to halfsend-01..12 but ADR 0040 was not updated. Carried forward from prior review.

  • [style] internal/forge/github/github.gocommitFilesWithRetry adds log.Printf for retry logging. This package has zero log.Printf calls on main and does not import "log" — it relies on error returns for observability. The new import breaks the package's zero-logging pattern.

  • [code-duplication] e2e/behaviour/drivers/ci/githubactions/githubactions.go / internal/forge/github/github.go — Two identical readLimited / readLimitedBytes functions (read limit+1, check length > limit). Consider extracting to a shared utility.

Previous run (5)

Review

Findings

Medium

  • [consumer-completeness] internal/config/config.go:447OrgConfigFromPerRepo does not copy the new PerRepoConfig.Runtime field to OrgConfig.Defaults.Runtime. When per-repo config is loaded via tryLoadFullsendConfig/requireFullsendConfig, the resulting OrgConfig will have an empty Defaults.Runtime, silently dropping the configured runtime. Currently latent — no active code path triggers this loss since run.go uses a separate path (backendFromConfigFile/resolveBackendFromConfigData), but any future consumer reading orgCfg.Defaults.Runtime from a per-repo-adapted OrgConfig will get an empty string instead of the configured value.
    Remediation: Update OrgConfigFromPerRepo to copy pr.Runtime: change Defaults: RepoDefaults{Roles: pr.Roles} to Defaults: RepoDefaults{Roles: pr.Roles, Runtime: pr.Runtime}.

  • [injection-vuln] internal/runtime/dummy.go — The url_get op passes user-supplied URLs to curl -sf <url> -o /dev/null without URL scheme validation. While shellQuote prevents shell injection, curl supports file://, gopher://, dict:// and other protocols. Sandbox network policy mitigates outbound risk, but file:// could probe sandbox-local files. The remediation is trivially cheap.
    Remediation: Add --proto =http,https to the curl command: curl --proto =http,https -sf %s -o /dev/null.

Low

  • [fail-open] internal/runtime/registry.go:13 — The dummy runtime is unconditionally available to any org that sets defaults.runtime: dummy. ADR 0065 documents this as intentional ("production orgs must not use dummy unintentionally"), and activation requires deliberate admin configuration. The risk is operational misconfiguration, not a technical fail-open.

  • [stale-reference] docs/ADRs/0040-org-pool-for-parallel-e2e-tests.md:36 — ADR 0040 references outdated pool org range "halfsend-01 through halfsend-06". The PR updates e2e-testing.md to halfsend-01..12 but ADR 0040 was not updated. The ADR's reference is descriptive context rather than a binding constraint.

  • [adr-amendment-scope] docs/ADRs/0003-org-config-repo-convention.md — Modifies the example config.yaml in the Decision section to add defaults.runtime and relabel the existing runtime: as "Legacy." ADR 0003 states "the exact schema will evolve" so updating the example is routine maintenance. Cross-reference to ADR 0065 is good practice.

  • [documentation-consistency] docs/ADRs/0003-org-config-repo-convention.md — The comment labels the existing runtime: section as "Legacy harness/model settings" but the section is not being deprecated. "Legacy" implies planned removal; a term like "Harness-specific" would be clearer since these settings (harness, model) are complementary to defaults.runtime, not superseded by it.

  • [breaking-api] internal/forge/forge.go:398forge.Client interface expanded with 6 new methods (AddIssueLabels, GetIssue, ListRecentWorkflowRuns, ListWorkflowRunArtifacts, DownloadWorkflowRunArtifact, ListRepositoryArtifacts). This is in internal/ so external breakage is impossible. FakeClient implements all new methods.

  • [error-handling-gaps] internal/cli/run.go — The backendFromConfigFile fallback path tries filepath.Join(filepath.Dir(path), ".fullsend", "config.yaml") when the primary path does not exist. This produces a nested .fullsend/.fullsend/config.yaml path that will never match in either install mode. Harmless — falls through to default claude runtime.

  • [error-message-style] internal/runtime/dummy.go — Error messages use raw operation names with underscores (read_file requires a path). Since these are the YAML-defined op names users reference in script files, using exact names aids debugging. Minor style preference.

  • [documentation-comment] e2e/behaviour/steps/dummy_agent.go — The steps package lacks a package-level godoc comment. This is new e2e test infrastructure — a doc comment would help future contributors understand the package's role.

  • [comment-style] e2e/behaviour/drivers/ci/githubactions/githubactions.go — Constants (pollInterval, dispatchWait, etc.) are self-documenting names but lack a brief block comment explaining the polling strategy.

  • [test-integrity] e2e/admin/auth_test.goTestMintEnrollProjectID_RespectsEnvOverride calls os.Unsetenv after t.Setenv, which is redundant since t.Setenv already restores the original value on cleanup.

Previous run (6)

Review

Reason: stale-head

The review agent reviewed commit 4c760fd8b01c949be0a2df373e0db89003e60541 but the PR HEAD is now 5d13d8b2d3facdb867c319eb00877b7b8a89d7b4. This review was discarded to avoid approving unreviewed code.

Previous run (7)

Review

3 findings (0 critical, 0 high, 3 medium) | Verdict: comment

Re-review of files changed since prior review at 9605c9c. Prior logic-error finding on dummy.go remains valid on unchanged code (severity anchored). No protected paths modified. Scope aligns with issue #2276.


Findings

Medium

  • [logic-error] internal/runtime/dummy.go:113DummyRuntime.Run returns execErr (the first operation-level error) as a non-nil Go error. In run.go (line 1011–1024), a non-nil runErr causes an early return that skips output file extraction (line 1050–1064), preventing behaviour-results.json from being extracted from the sandbox to the host. The “Sandbox blocks disallowed outbound URL” scenario in triage.feature expects url_get to fail, which sets execErr non-nil. writeBehaviourResults succeeds before the return (the file is in the sandbox), but host-side extraction is skipped, so any workflow step that uploads artifacts from the extracted output directory will miss the file. Unchanged since prior review; severity anchored.
    Remediation: Return nil instead of execErr from DummyRuntime.Run when writeBehaviourResults succeeds. Operation failures should be recorded in behaviour-results.json (via non-zero exitCode) rather than as a Go-level infrastructure error.

  • [adr-amendment-scope] docs/ADRs/0003-org-config-repo-convention.md:215 — ADR 0003 is Accepted on main. This PR modifies the example config.yaml schema in the Decision section by adding a new defaults: top-level key and renaming the existing runtime: section to “Legacy harness/model settings”. This introduces a new organizational concept not part of the original decision.
    Remediation: Keep ADR 0003 schema as-is and document defaults.runtime only in ADR 0065, or write a superseding ADR.

  • [stale-reference] docs/ADRs/0040-org-pool-for-parallel-e2e-tests.md:36 — ADR 0040 references outdated pool org range “halfsend-01 through halfsend-06”. The PR updates e2e-testing.md to halfsend-01..12 but ADR 0040 was not updated.
    Remediation: Update ADR 0040 to change “halfsend-06” to “halfsend-12”.

Low

  • [consumer-completeness] internal/config/config.go:451OrgConfigFromPerRepo constructs RepoDefaults{Roles: pr.Roles} without copying pr.Runtime to Defaults.Runtime. Any code path that converts a per-repo config to an org config via this function will silently lose the runtime selection, defaulting to claude. Currently latent — no active code path triggers this loss.

  • [fail-open] internal/config/config.go:302 — The dummy runtime is accepted as a valid runtime value in both org and per-repo config validation with no server-side guard restricting it to test orgs. The dummy runtime does not bypass sandbox security controls; it replaces LLM inference with scripted ops.

  • [edge-case] internal/forge/github/github.go:741commitFilesWithRetry retries exactly once on ErrNonFastForward. If the retry also hits a non-fast-forward, the error surfaces. Single retry covers the common auto_init race.

  • [scope-creep] internal/forge/forge.go:398 — PR adds new methods to forge.Client interface. Methods are only used by e2e/behaviour/ drivers.

  • [design-smell] docs/ADRs/0003-org-config-repo-convention.md:219defaults.runtime (backend selection: claude vs dummy) and runtime.harness (execution mode) create naming confusion. The PR labels runtime: as legacy but does not deprecate it.

  • [naming-convention] internal/runtime/dummy.go:19 — Constant behaviourScriptRelPath uses British spelling “behaviour” while Go convention typically prefers American English. The spelling is consistent across all 60+ files in this PR — a deliberate choice.

  • [error-handling-idiom] internal/runtime/dummy.go:75executeBehaviourScript collects firstErr and returns both results and error. The pattern is justified by the use case: the dummy runtime needs to record outcomes for all operations in behaviour-results.json, not just stop at first failure.

  • [api-shape] internal/forge/forge.go:398AddIssueLabels added without RemoveIssueLabels counterpart, creating asymmetric API. YAGNI applies — no caller currently needs the counterpart.

  • [code-organization] e2e/behaviour/steps/dummy_agent.go:144findModuleSubdir walks up directories to find go.mod using os.Getwd(). Fragile in tests but acceptable for test setup code.

  • [pattern-inconsistency] internal/runtime/registry.go:21 — Error message uses “unknown runtime” while config.go uses “invalid runtime”. Both wordings are defensible (config validation vs runtime resolution).

  • [code-organization] e2e/admin/cleanup.go:535funcLogger.Helper() is a no-op. A brief comment explaining why would improve readability.

  • [stale-reference] docs/ADRs/0060-cross-org-mint-authorization-via-org-variables.md:31 — ADR 0060 references “(halfsend-01, ...)” using open-ended ellipsis notation. Less stale than ADR 0040 since it does not commit to a specific upper bound.


Labels: PR adds Gherkin behaviour test framework with dummy runtime, touching e2e tests, runner infrastructure, per-repo install mode, and ADR documentation.

Previous run (8)

Review

1 finding (0 critical, 0 high, 1 medium) | Verdict: comment

Re-review of 19 files changed since prior review at a23a603. Prior scope-creep findings on reusable-triage.yml and run.go were withdrawn by the intent-coherence review — the per-repo install wiring is authorized by ADR 0063 and issue #2276. Prior logic-error finding on dummy.go remains valid on unchanged code (severity anchored). No protected paths modified in this version.


Findings

Medium

  • [logic-error] internal/runtime/dummy.go:113DummyRuntime.Run returns execErr (the first operation-level error) as a non-nil Go error. In run.go (line 1011–1024), a non-nil runErr causes an early return that skips output file extraction (line 1050–1064), preventing behaviour-results.json from being extracted from the sandbox to the host. The "Sandbox blocks disallowed outbound URL" scenario in triage.feature expects url_get to fail, which sets execErr non-nil. writeBehaviourResults succeeds before the return (the file is in the sandbox), but host-side extraction is skipped, so any workflow step that uploads artifacts from the extracted output directory will miss the file. Unchanged since prior review; severity anchored.
    Remediation: Return nil instead of execErr from DummyRuntime.Run when writeBehaviourResults succeeds. Operation failures should be recorded in behaviour-results.json (via non-zero exitCode) rather than as a Go-level infrastructure error.
Previous run (9)

Review — Comment

4 findings (0 critical, 0 high, 4 medium) | Verdict: comment

Re-review of 4 files changed since prior review at 47e3f6d. The prior logic-error finding on dummy.go remains valid on unchanged code (severity anchored). Two scope-creep findings were downgraded from high to medium by the challenger — the per-repo install infrastructure already exists on main, and the PR body explicitly mentions per-repo install mode.


Findings

Medium

  • [logic-error] internal/runtime/dummy.go:113DummyRuntime.Run returns execErr (the first operation-level error) as a non-nil Go error. In run.go, a non-nil runErr causes an early return that skips output file extraction, preventing behaviour-results.json from being extracted from the sandbox. For expected-failure scenarios (e.g., "Sandbox blocks disallowed outbound URL" in triage.feature), url_get is expected to fail, producing a non-nil execErr. This prevents the behaviour-results artifact from being uploaded, causing downstream assertion steps to fail. Unchanged since prior review; severity anchored.
    Remediation: Return nil instead of execErr from DummyRuntime.Run when writeBehaviourResults succeeds. Operation failures should be recorded in behaviour-results.json (via non-zero exitCode) rather than as a Go-level infrastructure error.

  • [scope-creep] .github/workflows/reusable-triage.yml:164 — Adds fullsend-dir conditional input based on install_mode. The per-repo install_mode infrastructure already exists on main (input at line 31, validation at line 88); this diff adds only the single-line passthrough to the action step. Minor wiring change, but not explicitly authorized by issue Add Gherkin behaviour tests with dummy runtime and CI job #2276.

  • [scope-creep] internal/cli/run.go:157backendFromConfigFile now falls back to .fullsend/config.yaml when the primary path does not exist. This is a production behavior change (4 lines, guarded by os.IsNotExist). The PR body mentions per-repo install mode but issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 does not explicitly authorize per-repo config resolution in the CLI.

  • [protected-path] .github/workflows/reusable-triage.yml — This PR modifies a file under .github/, a protected path. The linked issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 authorizes the behaviour test CI infrastructure and the PR description explains the rationale. Human approval is still required for protected-path changes regardless of context.

Previous run (10)

Review — Comment

2 findings (0 critical, 0 high, 2 medium) | Verdict: comment

This PR adds Gherkin behaviour tests with a dummy runtime, touching 63 files. The change is well-structured and aligns with the scope authorized by issue #2276. Two medium-severity findings are noted below for consideration.


Findings

1. internal/runtime/dummy.go:113 — logic-error (medium)

DummyRuntime.Run returns execErr (the first operation-level error) as a non-nil Go error. In run.go lines 1011–1024, a non-nil runErr causes an early return that skips output file extraction (line 1050+), preventing behaviour-results.json from being extracted from the sandbox.

For expected-failure scenarios (e.g., "Sandbox blocks disallowed outbound URL" in triage.feature), url_get is expected to fail, producing a non-nil execErr. This prevents the behaviour-results artifact from being uploaded, which would cause downstream assertion steps to fail because the artifact cannot be found.

Remediation: Return nil instead of execErr from DummyRuntime.Run when writeBehaviourResults succeeds. Operation failures should be recorded only in behaviour-results.json (via non-zero exitCode) rather than as a Go-level infrastructure error.

2. internal/cli/run.go:109 — defense-in-depth (medium)

resolveBackendFromConfigData reads the runtime selection from the org or per-repo config.yaml inside .fullsend/. Since this file lives in the repository and is writable by anyone with push access, a contributor could set runtime: dummy to disable the LLM inference layer. While repo-write access already implies significant trust and the sandbox security controls remain active, this silently removes prompt-level guardrails without operator notification.

Remediation: Consider a server-side or CI-level check that rejects dummy runtime in production contexts (e.g., restrict to known test org pool, or emit an audit log when dummy runtime is activated).


Notes

  • Re-review context: Prior review SHA ea1d08730cc130de6c132ba2438d2e4a40bb17d5 (app-verified). 5 files changed since prior review. The prior medium finding on dummy.go (logic-error) remains valid on unchanged code. The prior scope-creep finding on admin.go was resolved by the challenger (parameter naming is correct — the variable is runtimeName, not runtime).
  • No high/critical findings. Cross-repo contracts are all additive (internal packages). Intent aligns with issue Add Gherkin behaviour tests with dummy runtime and CI job #2276. No protected paths affected. No injection patterns detected in PR body.
  • Security positive notes: shellQuote correctly escapes single quotes, extractArtifactZip has proper zip-slip prevention, artifact downloads are capped at 50 MB via io.LimitReader.
Previous run (11)

Review

Findings

Medium

  • [logic-error] internal/runtime/dummy.go:113DummyRuntime.Run returns execErr (the first operation error) as a non-nil Go error. In run.go lines 1011-1024, a non-nil runErr causes early return, skipping output file extraction at line 1052 and the deferred post-script. For expected-failure scenarios (e.g., "Sandbox blocks disallowed outbound URL" in triage.feature), url_get is expected to fail, so execErr will be non-nil. This prevents behaviour-results.json from being extracted from the sandbox and uploaded as a GHA artifact, causing assertion steps to fail because the artifact cannot be found. The code comment says "run.go warns on non-zero exit but only aborts on a non-nil Go error (infrastructure failures)" — but then returns an operation failure as a Go error, contradicting that intent.
    Remediation: Return nil instead of execErr when writeBehaviourResults succeeds — operation failures are expected test outcomes, not infrastructure failures. Replace the final return exitCode, execErr with return exitCode, nil and let exitCode carry the operation-level signal.

  • [scope-creep] internal/cli/admin.go:567 — The --runtime flag on fullsend admin install and fullsend github setup adds permanent user-facing CLI API surface. Issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 authorizes defaults.runtime: dummy in config but does not explicitly authorize new CLI flags. The flag defaults to claude and is validated against ValidRuntimes(). The admin install path preserves existing runtime selection via loadExistingRuntime() to avoid accidental resets. While functionally sound, this is production API surface that the ci(e2e) prefix would exclude from release notes.
    Remediation: Consider whether the flag should be hidden (cobra.Command.Hidden) for non-test use cases, or document it as stable API if intended. If kept visible, the PR title should use a prefix that generates release notes for the new flag.

Previous run (12)

Review

Findings

Medium

  • [logic-error] internal/runtime/dummy.go:113DummyRuntime.Run returns execErr (the first operation error) as a non-nil Go error. In run.go lines 1011-1024, a non-nil runErr causes early return, skipping output file extraction at line 1052 and the deferred post-script. For expected-failure scenarios (e.g., "Sandbox blocks disallowed outbound URL" in triage.feature), url_get is expected to fail, so execErr will be non-nil. This prevents behaviour-results.json from being extracted from the sandbox and uploaded as a GHA artifact, causing assertion steps to fail because the artifact cannot be found. The comment at lines 4525-4526 says "run.go warns on non-zero exit but only aborts on a non-nil Go error" — but the code contradicts this by returning execErr.
    Remediation: Return nil instead of execErr when writeBehaviourResults succeeds — operation failures are expected test outcomes, not infrastructure failures. Replace the final return exitCode, execErr with return exitCode, nil and let exitCode carry the operation-level signal.

  • [commit-message] PR title ci(e2e): add Gherkin behaviour tests with dummy runtime uses ci prefix but introduces user-facing production features: --runtime CLI flag on admin install and github setup, defaults.runtime org config field, and per-repo runtime config field. Per COMMITS.md, ci is for CI/CD pipeline changes that do not appear in release notes. The CLI flags and config schema fields are permanent user-facing API surface that should appear in release notes.
    Remediation: Split into two commits: (1) feat(runtime): add runtime selection with dummy backend for testing covering CLI, config, registry, dummy impl; (2) ci(e2e): add Gherkin behaviour test suite with pluggable drivers covering e2e/behaviour/, ADR, docs. Alternatively, use a dual-scope title.


Labels: PR modifies internal/dispatch/gcf/ with Cloud Run mint discovery fallback alongside runtime and e2e changes.

Previous run (13)

Review

Findings

Medium

  • [logic-error] internal/runtime/dummy.go:113DummyRuntime.Run returns execErr (the first operation error) as a non-nil Go error. In run.go, a non-nil runErr causes early return at line 1024, skipping output file extraction (line 1052) and the deferred post-script (line 666). For expected-failure scenarios (e.g., "Sandbox blocks disallowed outbound URL" in triage.feature), url_get is expected to fail, so execErr will be non-nil, preventing behaviour-results.json from being extracted from the sandbox and uploaded as a GHA artifact. Behaviour test assertions that inspect results via ensureArtifacts will fail.
    Remediation: Return nil instead of execErr when writeBehaviourResults succeeds — operation failures are expected test outcomes, not infrastructure failures. Only return a non-nil Go error for true infrastructure problems (e.g., writeBehaviourResults failing). Replace the final return exitCode, execErr with return exitCode, nil and let exitCode carry the operation-level signal.

  • [commit-message] PR title ci(e2e): add Gherkin behaviour tests with dummy runtime uses ci prefix but introduces user-facing production features: --runtime CLI flag on admin install and github setup, defaults.runtime org config field, and per-repo runtime config field. Per COMMITS.md, ci is for CI/CD pipeline changes that do not appear in release notes. The CLI flags and config schema are permanent user-facing API surface that should appear in the Features section of release notes.
    Remediation: Split into two commits: (1) feat(runtime): add runtime selection with dummy backend for testing covering CLI, config, registry, dummy impl; (2) ci(e2e): add Gherkin behaviour test suite with pluggable drivers covering e2e/behaviour/, ADR, docs. Alternatively, use a dual-scope title if the PR is genuinely indivisible.

  • [scope-authorization] internal/cli/admin.go — Issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 authorizes "Dummy runtime, e2e/behaviour/ godog suite with triage vertical slice, CI job + repo secret, ADR 0063 and developer guides." The PR adds user-facing production features not explicitly mentioned in the issue: (1) CLI flags --runtime on admin install and github setup, (2) config schema fields defaults.runtime (org) and runtime (per-repo), (3) runtime registry pattern in internal/runtime/registry.go, (4) ValidRuntimes() config validation. These are permanent API surface — config schema fields cannot be removed without breaking changes, and CLI flags are user contracts.
    Remediation: Update issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 to explicitly authorize CLI flags, config schema additions, and the runtime registry pattern, OR file a separate issue for "Pluggable runtime selection infrastructure" and split the PR.

  • [missing-doc] docs/cli/README.md — The fullsend admin install command now accepts a --runtime flag, but there is no CLI documentation page for the admin command group. The docs/cli/ directory documents github, inference, and mint but omits admin. This is a pre-existing gap made worse by adding another undocumented flag.
    Remediation: Create docs/cli/admin.md documenting the admin command group including the new --runtime flag, or add the admin command to the groups table in docs/cli/README.md with a deprecation notice pointing to fullsend github setup.

  • [exported-function-naming] e2e/admin/cleanup.go:389 — New exported functions CleanupStaleResources, TeardownPerRepoInstall, and ReleaseLock are thin wrappers around previously-unexported functions in the admin package, exported to share with behaviour tests. This expands the admin package's exported API surface to serve a different test suite.
    Remediation: Consider creating a dedicated e2e/shared package for helpers that need to be shared between admin and behaviour tests, rather than expanding the admin package's public API.

Previous run (14)

Review

Findings

Medium

  • [logic-error] internal/runtime/dummy.goDummyRuntime.Run returns execErr (the first operation error) as a non-nil Go error. In run.go, a non-nil runErr aborts the workflow early, skipping the post-script that uploads artifacts to GitHub Actions. For scenarios where operations are expected to fail (e.g., "Sandbox blocks disallowed outbound URL" in triage.feature), this prevents behaviour-results.json from being available as a GHA artifact, causing assertion steps to fail.
    Remediation: Return nil error for expected operation failures; only return non-nil for infrastructure failures (e.g., writeBehaviourResults failing). Replace the final return exitCode, execErr with return exitCode, nil and let exitCode carry the operation-level signal.

  • [commit-message] PR title ci(e2e): add Gherkin behaviour tests with dummy runtime uses ci prefix but introduces user-facing production features: --runtime CLI flag on admin install and github setup, defaults.runtime org config field, and per-repo runtime config field. Per COMMITS.md, ci is for CI/CD pipeline changes that do not appear in release notes. The --runtime flag is a user-invocable feature that should be discoverable in release notes. The COMMITS.md forbidden combinations table maps feat(e2e)ci(e2e), which covers the test infrastructure, but not the runtime selection mechanism itself.
    Remediation: Consider feat(runtime): add pluggable runtime selection with dummy backend for behaviour tests, or if keeping ci scope, document the --runtime flag addition separately so it appears in release notes.


Labels: PR adds runtime selection config/CLI (production feature) alongside Gherkin test infrastructure

Previous run (15)

Review

Findings

High

  • [commit-message] PR title ci(e2e): add Gherkin behaviour tests with dummy runtime uses ci prefix but adds user-facing production features: defaults.runtime org config field, --runtime CLI flag on fullsend admin install and fullsend github setup, and runtime resolution logic in internal/cli/run.go. Per COMMITS.md, ci is for CI/CD pipeline changes that do not appear in release notes. A new CLI flag users can invoke is feat scope. Using ci(e2e) would hide the --runtime flag and defaults.runtime config from release notes.
    Remediation: Use feat(runtime): add pluggable runtime selection with dummy backend or similar feat prefix. If the e2e scope must be kept, use feat(e2e) — though COMMITS.md forbids feat(e2e) in favor of ci(e2e), the scope itself (e2e) miscategorizes user-facing runtime selection. Consider feat(runtime) with a description covering both the config API and the test infrastructure.

Medium

  • [error-handling] internal/cli/run.go — The isOrgConfigData heuristic distinguishes org vs per-repo config using string matching on header comments followed by YAML probing for org-specific keys (dispatch, defaults, repos). A manually crafted or corrupted org config without the standard header comment and without the probed keys would be misidentified as per-repo config, causing ResolveFromPerRepoConfig to parse it and silently ignore defaults.runtime. Machine-generated configs (via NewOrgConfig) always include dispatch and defaults, so this affects only hand-edited edge cases.
    Remediation: Add an explicit discriminator field (e.g., config_type: org vs config_type: per-repo), or try per-repo parse first (it has a stricter header check) and fall back to org config only on failure.

  • [scope-coherence] This PR bundles three separable concerns in 57 files: (1) runtime selection and configuration — a production feature adding defaults.runtime to org config, runtime to per-repo config, --runtime to CLI, and resolution logic in the runner; (2) dummy runtime implementation — the DummyRuntime type in internal/runtime/; (3) Gherkin behaviour test infrastructure — the entire e2e/behaviour/ framework with drivers, steps, and CI job. While thematically related and authorized by issue Add Gherkin behaviour tests with dummy runtime and CI job #2276, the production config/CLI changes (concern 1) are architecturally distinct from the test infrastructure (concern 3) and would benefit from separate review cycles.

Previous run (16)

Review

Findings

Medium

  • [protected-path] .github/workflows/e2e.yml — This PR modifies a file under .github/, a protected path requiring human approval. The linked issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 explicitly authorizes adding a behaviour test CI job with E2E_ISSUE_AUTHOR_TOKEN and EVAL_GH_TOKEN secrets, and the PR description explains the rationale. Human approval is still required for protected-path changes regardless of context.

  • [architectural-violation] docs/ADRs/0003-org-config-repo-convention.md:215 — The PR adds defaults.runtime for backend selection (claude vs dummy) alongside the existing runtime.harness and runtime.model fields for execution parameters. The added comment labels the existing fields as "Legacy harness/model settings" but no deprecation plan exists. While the two concepts are genuinely distinct (backend engine vs execution parameters), the naming overlap between defaults.runtime and the runtime section creates potential confusion for operators configuring orgs.
    Remediation: Clarify the relationship between defaults.runtime and runtime.harness/runtime.model in ADR 0062, or remove the "Legacy" label from the comment if no deprecation is planned.

  • [config-validation] internal/config/config.go — The org config uses defaults.runtime while per-repo config uses a top-level runtime field. Both accept the same ValidRuntimes() values but the precedence model is not documented. ResolveFromConfig() reads from org config while ResolveFromPerRepoConfig() reads from per-repo config — these are called from different code paths in run.go, making the inheritance implicit. If an org sets defaults.runtime: claude and a per-repo config sets runtime: dummy, the per-repo config wins because backendFromConfigFile tries isOrgConfigData first, then falls through to per-repo parsing — but this is not documented.
    Remediation: Add a comment in resolveBackendFromConfigData or ADR 0062 explaining the per-repo-overrides-org precedence for runtime selection.

Previous run (17)

Looks good to me

Previous run (18)

Review

Findings

Medium

  • [commit-message-prefix] PR title uses feat(e2e) but this PR adds both user-facing features (defaults.runtime config key, --runtime CLI flag on both admin install and github setup commands) and test infrastructure (dummy runtime, e2e/behaviour/ test suite). Per COMMITS.md, feat is for new user-facing functionality and scope should identify the subsystem. The scope e2e is misleading since the user-facing runtime selection feature is not e2e-scoped.
    Remediation: Consider feat(runtime): add pluggable runtime selection with dummy backend or split into separate commits for the runtime API (feat(runtime)) and test infrastructure (test(e2e)).

  • [missing-doc] docs/cli/github.md:45 — The --runtime flag added to fullsend github setup (per-repo mode) is missing from the flags table in the CLI reference. Users looking up available flags would not discover this option.
    Remediation: Add a row to the flags table: | --runtime | | Agent runtime backend (claude or dummy) |

Previous run (19)

Review

Findings

High

  • [adr-reference-error] docs/ADRs/0003-org-config-repo-convention.md:215 — Comment references "ADR 0043 for behaviour-test dummy runtime" but ADR 0043 is "Add upstream source headers to managed scaffold files" and has nothing to do with behaviour tests or the dummy runtime. ADR 0062 ("Behaviour tests with Gherkin and pluggable drivers"), which this PR creates, is the correct reference.
    Remediation: Change see ADR 0043 to see ADR 0062.

Medium

  • [edge-case] internal/cli/run.goisOrgConfigData returns true (treating data as org config) when YAML parsing fails. A malformed per-repo config would be misclassified as org config, causing resolveBackendFromConfigData to attempt org-config parsing and produce a confusing error message. The downstream parse will also fail, so no silent corruption occurs, but the user experience is misleading.
    Remediation: Return false on YAML parse failure so the per-repo parse path is attempted first, or return both parse errors to the caller.

  • [commit-message-prefix] — PR title uses feat(e2e) but this PR adds both user-facing features (defaults.runtime config key, --runtime CLI flag, runtime.ResolveFromConfig()) and test infrastructure (dummy runtime, e2e/behaviour/ test suite). Per COMMITS.md, feat is for new user-facing functionality and test is for adding/updating tests. feat(e2e) miscategorizes the scope — the user-facing feature is runtime selection, not the e2e tests themselves.
    Remediation: Consider feat(runtime): add pluggable runtime selection with dummy backend or split into separate commits for the runtime API (feat(runtime)) and test infrastructure (test(e2e)).

  • [missing-doc] docs/cli/github.md:45 — The --runtime flag added to fullsend github setup (per-repo mode) is missing from the flags table in the CLI reference. Users looking up available flags would not discover this option.
    Remediation: Add a row to the flags table: | --runtime | | Agent runtime backend (claude or dummy) |

  • [missing-doc] docs/runtimes.md — The document references fullsend admin install --runtime dummy but the --runtime flag is not documented in any CLI reference page. No docs/cli/admin.md exists for admin command flags.
    Remediation: Create a CLI reference for fullsend admin install flags, or add a cross-reference to where --runtime is documented.

Previous run (20)

Review

Findings

High

  • [adr-reference-error] docs/ADRs/0003-org-config-repo-convention.md:215 — Comment references "ADR 0043 for behaviour-test dummy runtime" but ADR 0043 is "Add upstream source headers to managed scaffold files" and has nothing to do with behaviour tests or the dummy runtime. ADR 0062 ("Behaviour tests with Gherkin and pluggable drivers") is the correct reference.
    Remediation: Change see ADR 0043 to see ADR 0062.

Medium

  • [protected-path] .github/workflows/e2e.yml — This PR modifies .github/workflows/e2e.yml, a protected path requiring human approval. The linked issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 explicitly authorizes adding a behaviour test CI job, and the PR description explains the rationale. Human approval is still required for protected-path changes regardless of context.

  • [missing-doc] docs/cli/github.md:45 — The --runtime flag added to fullsend github setup (per-repo mode) is missing from the flags table in docs/cli/github.md. Users looking up available flags would not discover this option.
    Remediation: Add a row to the flags table: | --runtime | | Agent runtime for per-repo config (claude or dummy; dummy for behaviour test orgs only) |

  • [edge-case] internal/cli/run.go:131isOrgConfigData defaults to returning true (treating data as org config) when YAML parsing fails, and relies on probing for org-specific fields (dispatch, defaults, repos) to distinguish config types. A per-repo config that fails to parse (malformed YAML) would be misclassified as org config. The heuristic is fragile if future per-repo configs gain fields that overlap with the probe struct.
    Remediation: Invert the default: return false when parsing fails or no org-specific fields are found, so the caller falls through to ParsePerRepoConfig.

Previous run (21)

Review

Findings

High

  • [adr-reference-error] docs/ADRs/0003-org-config-repo-convention.md:215 — Comment references "ADR 0043 for behaviour-test dummy runtime" but ADR 0043 is "Add upstream source headers to managed scaffold files" and has nothing to do with behaviour tests or the dummy runtime. ADR 0062 ("Behaviour tests with Gherkin and pluggable drivers") is the correct reference.
    Remediation: Change see ADR 0043 to see ADR 0062.

Medium

  • [protected-path] .github/workflows/e2e.yml — This PR modifies .github/workflows/e2e.yml, a protected path requiring human approval. The linked issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 explicitly authorizes adding a behaviour test CI job, and the PR description explains the rationale. Human approval is still required for protected-path changes regardless of context.

  • [missing-doc] docs/cli/github.md:45 — The --runtime flag added to fullsend github setup (per-repo mode) is missing from the flags table in docs/cli/github.md. Users looking up available flags would not discover this option.
    Remediation: Add a row to the flags table: | --runtime | | Agent runtime for per-repo config (claude or dummy; dummy for behaviour test orgs only) |

  • [edge-case] internal/cli/run.go:131isOrgConfigData defaults to returning true (treating data as org config) when YAML parsing fails, and relies on probing for org-specific fields (dispatch, defaults, repos) to distinguish config types. A per-repo config that fails to parse (malformed YAML) would be misclassified as org config. The heuristic is fragile if future per-repo configs gain fields that overlap with the probe struct.
    Remediation: Invert the default: return false when parsing fails or no org-specific fields are found, so the caller falls through to ParsePerRepoConfig.

Previous run (22)

Review

Findings

Critical

  • [interface-mismatch] internal/runtime/dummy.go:80DummyRuntime.Run signature is Run(params RunParams, printer *ui.Printer, _ time.Time, _ *RunMetrics) (int, error) but the Runtime interface at runtime.go:43 requires Run(ctx context.Context, params RunParams, printer *ui.Printer, start time.Time, metrics *RunMetrics) (exitCode int, err error). The missing ctx context.Context first parameter means DummyRuntime does not satisfy the Runtime interface, causing a compilation failure. The registry at registry.go:16 assigns DummyRuntime{} to Backend.Runtime (a Runtime interface value), so the compiler will reject it.
    Remediation: Add ctx context.Context as the first parameter: func (r DummyRuntime) Run(ctx context.Context, params RunParams, printer *ui.Printer, _ time.Time, _ *RunMetrics) (int, error).

Medium

  • [protected-path] .github/workflows/e2e.yml — This PR modifies .github/workflows/e2e.yml, a protected path requiring human approval. The linked issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 explicitly authorizes adding a behaviour test CI job, and the PR description explains the rationale. Human approval is still required for protected-path changes regardless of context.

  • [forge-abstraction-gap] e2e/behaviour/drivers/scm/github/github.go:45 — Direct GitHub API call in GetIssue to fetch issue details with labels bypasses the forge.Client abstraction. AGENTS.md prohibits direct GitHub REST calls outside internal/forge/github/. forge.Client lacks a GetIssue method with label support. The e2e/admin package already has the same pattern (getRepoCreatedAt in testutil.go), establishing an implicit exception for e2e test code. See also: [forge-abstraction-gap] finding for githubactions.go artifact downloads.

Previous run (23)

Review

Findings

Critical

  • [interface-mismatch] internal/runtime/dummy.go:80DummyRuntime.Run signature is Run(params RunParams, printer *ui.Printer, _ time.Time, _ *RunMetrics) (int, error) but the Runtime interface at runtime.go:43 requires Run(ctx context.Context, params RunParams, printer *ui.Printer, start time.Time, metrics *RunMetrics) (exitCode int, err error). The missing ctx context.Context first parameter means DummyRuntime does not satisfy the Runtime interface, causing a compilation failure. The registry at registry.go:16 assigns DummyRuntime{} to Backend.Runtime (a Runtime interface value), so the compiler will reject it.
    Remediation: Add ctx context.Context as the first parameter: func (r DummyRuntime) Run(ctx context.Context, params RunParams, printer *ui.Printer, _ time.Time, _ *RunMetrics) (int, error).

Medium

  • [protected-path] .github/workflows/e2e.yml — This PR modifies .github/workflows/e2e.yml, a protected path requiring human approval. The linked issue Add Gherkin behaviour tests with dummy runtime and CI job #2276 explicitly authorizes adding a behaviour test CI job, and the PR description explains the rationale. Human approval is still required for protected-path changes regardless of context.

  • [forge-abstraction-gap] e2e/behaviour/drivers/scm/github/github.go:45 — Direct GitHub API call in GetIssue to fetch issue details with labels bypasses the forge.Client abstraction. AGENTS.md prohibits direct GitHub REST calls outside internal/forge/github/. forge.Client lacks a GetIssue method with label support. The e2e/admin package already has the same pattern (getRepoCreatedAt in testutil.go), establishing an implicit exception for e2e test code. See also: [forge-abstraction-gap] finding for githubactions.go artifact downloads.

Previous run (24)

Review

Findings

High

  • [protected-path] .github/workflows/e2e.yml — This PR modifies files under .github/, a protected path requiring human approval. No linked issue exists to provide authorization context for this change. The PR description explains the CI job addition, but protected-path changes require an explicit issue link justifying the modification.
    Remediation: File an issue describing the need for the behaviour test CI job, link it to this PR, and obtain human approval for the .github/ change.

Medium

  • [command-injection] internal/runtime/dummy.go:159 — The run_command op passes op.Args directly to sandbox.Exec without sanitization or allowlisting. Other ops (read_file, url_get) use shellQuote for their arguments. While execution occurs inside a sandbox and the behaviour script requires config repo write access to plant, this creates an unsanitized command execution path that other ops deliberately avoid.
    Remediation: Either remove run_command (other ops cover the test scenarios), restrict to an explicit allowlist of safe commands, or at minimum apply shellQuote consistently.

  • [misconfiguration-guard] internal/runtime/registry.go:16 — The dummy runtime is registered unconditionally in the production binary. Any org whose config.yaml has defaults.runtime: dummy will bypass LLM inference and execute behaviour script ops instead. There is no guardrail preventing a production org from being (accidentally or maliciously) configured with runtime: dummy. The env.go validator only runs during the e2e test suite, not in the fullsend run production path.
    Remediation: Add a runtime environment guard (e.g., require FULLSEND_ALLOW_DUMMY_RUNTIME=1) so the dummy runtime cannot be activated in production CI runs without explicit opt-in.

  • [exit-code-contract] internal/runtime/dummy.go:115DummyRuntime.Run returns exitCode=1 when an operation fails, but executeBehaviourScript always returns nil error. The caller in run.go only aborts on a non-nil Go error — a non-zero exit code is just a warning. This means a failed operation is treated as non-fatal, diverging from how ClaudeRuntime treats non-zero exits.
    Remediation: Return a Go error from Run when any operation fails (matching ClaudeRuntime behavior), or document that dummy runtime exit code 1 is intentionally non-fatal.

  • [missing-authorization] — No linked issue for this 1800+ line feature PR that adds significant testing infrastructure (Gherkin behaviour tests, dummy runtime, pluggable drivers, CI job, ADR, 6 doc files). While ADR 0043 (included in the PR) provides design authorization, non-trivial changes to protected paths require an explicit issue link per project governance.
    Remediation: File an issue proposing this testing infrastructure addition and link it to the PR.

  • [stale-doc] docs/ADRs/0003-org-config-repo-convention.md:216 — The config schema example shows a nested runtime: section with harness and model fields. This PR introduces defaults.runtime as a flat string field (claude or dummy). The example config is now misleading for users configuring runtime selection.
    Remediation: Update the example config to show defaults.runtime: claude under the defaults: section.

  • [missing-doc] docs/guides/getting-started/installation.md:240 — The admin install flags table does not document the new --runtime flag. Users installing behaviour test orgs need fullsend admin install --runtime dummy, but this flag is absent from the primary installation reference.
    Remediation: Add a row to the flags table: | --runtime | claude | Agent runtime backend (claude or dummy); dummy is for behaviour test orgs only |

fullsend-ai-review[bot]

This comment was marked as outdated.

@ifireball

Copy link
Copy Markdown
Member Author

About the @fullsend-ai-review "[misconfiguration-guard]" comment - I reject adding extra layers, users having to explicitly commit the "dummy" value into their configuration should be enough to convince us they know what they are doing if they do it.

@ifireball
ifireball requested review from ralphbean and waynesun09 and removed request for ralphbean June 7, 2026 11:35

@ralphbean ralphbean 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.

I think this needs a few changes before we can merge. See inline comments.

Comment thread docs/ADRs/0043-behaviour-tests-with-gherkin-and-drivers.md Outdated
Comment thread e2e/behaviour/steps/triage.go Outdated
Comment thread e2e/behaviour/steps/triage.go Outdated
Comment thread e2e/behaviour/drivers/ci/githubactions/githubactions.go Outdated
Comment thread internal/runtime/dummy.go
Comment thread e2e/behaviour/drivers/ci/githubactions/githubactions.go Outdated
Comment thread internal/runtime/dummy.go
Comment thread e2e/behaviour/steps/dummy_agent.go Outdated
Comment thread .github/workflows/e2e.yml
Comment thread e2e/behaviour/suite_test.go

@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.

Review squad findings (6 agents, deduplicated against 13 existing review threads). 1 original CRITICAL finding dropped as false positive (claude.go not modified in this PR). 5 new findings posted — 1 HIGH, 4 MEDIUM.

Comment thread e2e/behaviour/steps/triage.go Outdated
Comment thread internal/runtime/dummy.go Outdated
Comment thread e2e/behaviour/drivers/ci/githubactions/githubactions.go
Comment thread .github/workflows/e2e.yml
Comment thread e2e/behaviour/drivers/ci/githubactions/githubactions.go Outdated
@ifireball

Copy link
Copy Markdown
Member Author

Review follow-up in 045a2596 + 2d56089a:

  • Assertion step ordering (HIGH): fixed — assertion steps verify immediately; workflow step only waits for success.
  • Protected path / authorization: tracked in Add Gherkin behaviour tests with dummy runtime and CI job #2276 (Closes #2276 on PR).
  • All inline threads: replied and resolved with fixes or maintainer decisions documented.

Remaining coordination: ADR 0043 renumber when #1816 merges second.

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 14, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:28 PM UTC · Completed 5:42 PM UTC
Commit: 2d56089 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@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.

Review Squad Report (8 agents dispatched, 14 findings after dedup, 4 false positives removed)

3 HIGH, 5 MEDIUM findings posted inline. The one CRITICAL finding reported by multiple agents (DummyRuntime.Run missing context.Context) was verified as a false positive — the Runtime interface does not require ctx in Run().

Comment thread e2e/behaviour/steps/cleanup.go
Comment thread internal/runtime/dummy.go Outdated
Comment thread e2e/behaviour/steps/triage.go Outdated
Comment thread e2e/behaviour/steps/cleanup.go Outdated
Comment thread internal/runtime/dummy.go Outdated
Comment thread e2e/behaviour/drivers/ci/githubactions/githubactions.go Outdated
Comment thread e2e/behaviour/artifacts/find.go
Comment thread e2e/behaviour/suite_test.go
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 30, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 10:35 AM UTC · Completed 11:00 AM UTC
Commit: 54c3af9 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 12:24 PM UTC · Ended 12:28 PM UTC
Commit: 104508d · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 12:31 PM UTC · Ended 12:31 PM UTC
Commit: 104508d · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 12:34 PM UTC · Ended 12:35 PM UTC
Commit: 104508d · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 12:38 PM UTC · Ended 12:48 PM UTC
Commit: 104508d · View workflow run →

Main already has ADR 0065 (provider-backed policy composition); renumber
the behaviour-tests ADR and update cross-references.

Signed-off-by: Barak Korren <bkorren@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:07 PM UTC · Completed 7:20 PM UTC
Commit: 0b57325 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

r := ClaudeRuntime{}
return Backend{Runtime: r, Transcripts: r}, nil
case "dummy":
// Selected only via explicit per-repo/org config (behaviour test orgs).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] fail-open

The dummy runtime is unconditionally available to any org that sets defaults.runtime: dummy. ADR 0066 documents this as intentional; activation requires deliberate admin configuration.

return false, ctx.Err()
case <-time.After(jitter):
}
log.Printf("retrying commit to %s/%s@%s (attempt %d/%d): %v", owner, repo, branch, attempt+1, maxAttempts, 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.

[low] pattern-inconsistency

commitFilesWithRetry adds log.Printf for retry logging. This package has zero log.Printf calls on main and does not import log — it relies on error returns for observability. The new import breaks the zero-logging pattern.

Suggested fix: Remove the log.Printf call. The error is already returned to the caller.

fn, err := p.gcpAPI.GetFunction(ctx, p.cfg.ProjectID, p.cfg.Region, functionName)
mintURI, err := p.resolveMintURI(ctx)
if err != nil {
return fmt.Errorf("getting mint function: %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.

[low] dead-code

In EnsureOrgInMint, the mintURI == empty check after resolveMintURI is unreachable. resolveMintURI calls DiscoverMint, which always returns an error when the URI is empty — the function never returns (empty, nil).

Suggested fix: Remove the unreachable mintURI == empty check and its error return.

@fullsend-ai-retro

fullsend-ai-retro Bot commented Jul 8, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 7:27 PM UTC · Completed 7:37 PM UTC
Commit: 0b57325 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

PR #1982 (Gherkin behaviour tests with dummy runtime) was a large human-authored PR (66 files, +4492/-116) that took 31 days to merge. The review process was thorough but extremely expensive: 60 review bot events (33 cancelled, 20 successful), consuming an estimated 9+ hours of review compute. Key issues: (1) 33 cancelled reviews from rapid-push dispatch storms, (2) the same dismissed finding re-raised 4 times, (3) 15 post-approval review runs adding no critical value, and (4) the most critical bug was found by the review squad, not the automated agent. Three proposals filed as evidence for existing open issues.

Proposals filed

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

Labels

component/ci CI pipelines and checks component/dispatch Workflow dispatch and triggers component/docs User-facing documentation component/e2e End-to-end tests component/install CLI install and app setup component/runner Agent runner behavior and lifecycle go Pull requests that update go code requires-manual-review Review requires human judgment testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Gherkin behaviour tests with dummy runtime and CI job

6 participants