Skip to content

fix(#3396): propagate default allowlist to harness composition - #3425

Merged
ralphbean merged 4 commits into
mainfrom
fix/3396-propagate-default-allowlist
Jul 7, 2026
Merged

fix(#3396): propagate default allowlist to harness composition#3425
ralphbean merged 4 commits into
mainfrom
fix/3396-propagate-default-allowlist

Conversation

@ralphbean

Copy link
Copy Markdown
Member

Summary

  • Fix allowlist-default asymmetry introduced by PR feat(cli): add runtime fallback to agents repo for unconfigured agents #2947: tryAgentsRepoFallback defaulted OrgAllowlist internally for its own fetch but didn't propagate it into composeOpts, causing LoadWithBaseresolveBaseScriptsfetchBaseFile to reject pre_script/post_script URLs with "not in allowed_remote_resources"
  • Add propagateDefaultAllowlist() in runAgent between resolveAgentSource and LoadWithBase to fill the default when no config.yaml supplied an allowlist
  • Affects any install with no config.yaml (or one missing allowed_remote_resources) that relies on the agents-repo fallback — including fullsend's own CI

Related Issue

Closes #3396

Changes

  • internal/cli/run.go: Add propagateDefaultAllowlist() and call it in runAgent after setting SourceURL
  • internal/cli/run_test.go: Unit test for propagateDefaultAllowlist (nil → default, non-nil → preserved) and integration test confirming LoadWithBase succeeds with propagated allowlist but fails without

Testing

  • Unit tests pass: TestPropagateDefaultAllowlist, TestAgentsRepoFallback_LoadWithBase_NilAllowlist
  • Integration test confirms the bug (nil allowlist → error) and the fix (propagated allowlist → success)
  • go vet clean
  • make lint clean
  • CI functional-tests job should go green — the 4 triage eval cases that have been failing since PR feat(cli): add runtime fallback to agents repo for unconfigured agents #2947 merged

Checklist

  • Tests added
  • Linter passes
  • Commit message follows COMMITS.md conventions

🤖 Generated with Claude Code

tryAgentsRepoFallback defaults OrgAllowlist to
config.DefaultAllowedRemoteResources() for its own fetch, but didn't
propagate that default back into composeOpts. Downstream,
LoadWithBase → resolveBaseScripts → fetchBaseFile rejected
pre_script/post_script URLs because OrgAllowlist was still nil.

Add propagateDefaultAllowlist() to fill the default when no
config.yaml supplied an allowlist, called in runAgent between
resolveAgentSource and LoadWithBase.

Closes #3396

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@ralphbean
ralphbean requested a review from a team as a code owner July 7, 2026 21:09
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix agents-repo fallback by propagating default allowlist into ComposeOpts

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Propagate the default allowed remote resources into harness composition options.
• Fix agents-repo fallback runs failing to fetch pre/post scripts due to nil allowlist.
• Add unit + integration coverage for the nil-allowlist regression and the fix.
Diagram

graph TD
A["CLI runAgent"] --> B["resolveAgentSource"] --> C["tryAgentsRepoFallback"] --> D["propagateDefaultAllowlist"] --> E["harness.LoadWithBase"] --> F["Fetch pre/post scripts"]
G["config.DefaultAllowedRemoteResources"] -.-> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Default OrgAllowlist inside harness.LoadWithBase
  • ➕ Guarantees consistent behavior for all callers (not just CLI runAgent).
  • ➕ Reduces reliance on callers to remember to set allowlist defaults.
  • ➖ Changes library semantics and may broaden trust defaults unexpectedly.
  • ➖ Harder to keep policy decisions in CLI/config layer.
2. Have tryAgentsRepoFallback set composeOpts.OrgAllowlist when nil
3. Introduce a ComposeOpts constructor/normalizer used by all call sites
  • ➕ Centralizes defaulting rules and reduces future drift.
  • ➕ Scales if more defaults/validation are added over time.
  • ➖ More refactor than needed for a narrow regression fix.
  • ➖ Requires updating multiple call sites and potentially public API patterns.

Recommendation: The chosen approach (normalize ComposeOpts in runAgent via propagateDefaultAllowlist right before LoadWithBase) is a good balance: it fixes the observed asymmetry with minimal surface-area change, keeps policy in the CLI/config layer, and is covered by focused regression tests. Consider a shared ComposeOpts normalizer only if additional defaulting rules accumulate.

Files changed (2) +104 / -0

Bug fix (1) +17 / -0
run.goPropagate default allowlist into ComposeOpts before harness composition +17/-0

Propagate default allowlist into ComposeOpts before harness composition

• Adds propagateDefaultAllowlist() to fill ComposeOpts.OrgAllowlist when nil using config.DefaultAllowedRemoteResources(). Calls this helper in runAgent after SourceURL resolution so LoadWithBase/resolveBaseScripts sees a consistent allowlist (fixing #3396).

internal/cli/run.go

Tests (1) +87 / -0
run_test.goAdd unit + integration regression tests for nil-allowlist fallback composition +87/-0

Add unit + integration regression tests for nil-allowlist fallback composition

• Adds unit coverage ensuring nil allowlist gets defaulted and non-nil values are preserved. Adds an integration-style test reproducing the failure mode (nil OrgAllowlist rejects script fetch) and validating the fix path (allowlist propagated, scripts resolved to local cache paths).

internal/cli/run_test.go

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Site preview

Preview: https://16449ba1-site.fullsend-ai.workers.dev

Commit: f251f09b544896573e831434d6e0431b50bb4acd

@qodo-code-review

qodo-code-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 54 rules

Grey Divider


Informational

1. propagateDefaultAllowlist ignores empty allowlist 📎 Requirement gap ☼ Reliability
Description
propagateDefaultAllowlist only checks opts.OrgAllowlist == nil, so an explicitly empty allowlist
(len==0) will not be defaulted and downstream LoadWithBase can still fail with `not in
allowed_remote_resources`. This does not fully meet the requirement to propagate defaults when the
allowlist is nil/empty after agents-repo fallback.
Code

internal/cli/run.go[R2694-2697]

+func propagateDefaultAllowlist(opts *harness.ComposeOpts) {
+	if opts.OrgAllowlist == nil {
+		opts.OrgAllowlist = config.DefaultAllowedRemoteResources()
+	}
Relevance

⭐ Low

PR #2947 explicitly treats empty allowlist as “deny all”; defaulting len==0 would break that
established semantic.

PR-#2947

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1525850 requires propagating the default allowlist when composeOpts.OrgAllowlist
is nil/empty. The added helper only checks for nil, and tryAgentsRepoFallback similarly only
defaults allowlist when it is nil, leaving the empty-slice case non-compliant with the rule’s
stated condition.

Propagate default allowed_remote_resources after agents-repo fallback succeeds
internal/cli/run.go[2694-2698]
internal/cli/run.go[2721-2724]

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

## Issue description
`propagateDefaultAllowlist` only defaults when `OrgAllowlist` is `nil`. The compliance requirement calls out `nil/empty` allowlists; an empty slice can still lead to downstream harness URL resolution failures.

## Issue Context
`tryAgentsRepoFallback` and the new propagation helper both treat only `nil` as missing. If `OrgAllowlist` becomes an empty slice (e.g., from config parsing or callers constructing `ComposeOpts{OrgAllowlist: []string{}}`), defaults won’t be propagated.

## Fix Focus Areas
- internal/cli/run.go[2694-2698]
- internal/cli/run.go[2721-2724]
- internal/cli/run_test.go[3497-3509]

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


Grey Divider

Qodo Logo

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ralphbean
ralphbean enabled auto-merge July 7, 2026 21:39

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

Verified the root-cause analysis against the code and the CI history — the diagnosis is correct and the fix is the right minimal change: tryAgentsRepoFallback defaulted a nil allowlist only for its own fetch, while LoadWithBaseresolveBaseScripts saw the still-nil OrgAllowlist and rejected the harness's own script URLs.

Three inline comments:

  1. Keep the nil-only check (the bot's suggestion to also default empty slices would break the deny-all contract), but document the semantic change for configs that omit the key.
  2. The integration test overwrites the propagated allowlist, so nothing asserts the real default list matches the fallback's URL shape — one extra assertion closes that.
  3. The green functional-tests check on this PR is a path-filter skip, not a passinternal/cli/ isn't in the relevance filter (the same gap that let #2947 break main silently). Adding internal/scaffold/fullsend-repo/config.yaml (fix 2 from #3396) to this PR would both harden CI's dogfood path and make the evals actually run pre-merge.

The e2e failure is unrelated infra flake — apt-get install podman in the test-org workflow hit a bad packages.microsoft.com repo signature before the fullsend binary was exercised.

Comment thread internal/cli/run.go
Comment on lines +2694 to +2697
func propagateDefaultAllowlist(opts *harness.ComposeOpts) {
if opts.OrgAllowlist == nil {
opts.OrgAllowlist = config.DefaultAllowedRemoteResources()
}

@waynesun09 waynesun09 Jul 7, 2026

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 — review-squad consensus update] A 4-agent review squad (Claude, Gemini, Codex reviewers) independently converged on this spot, elevating my earlier note: the unconditional propagation is a behavior change to a deny-by-default security boundary, shipped untested.

Concretely: when config.yaml loads successfully but omits allowed_remote_resources (YAML-omitted → nil), the orgCfg == nil recheck below is skipped, so a URL-base: disk harness pointing at the fullsend-ai/* prefixes now fetches and executes its remote pre/post scripts where LoadWithBase previously hard-rejected with "URL base requires org-level allowed_remote_resources" (the ADR 0058 anti-self-authorization guard). The no-config.yaml-at-all case is still protected (requireOrgConfig hard-errors), and the injected default is compile-time first-party constants — so this is a policy-control loss, not a new execution primitive. But it deserves an explicit decision rather than riding along:

  • Either scope the propagation to the path that needs it — e.g. only when the harness came from tryAgentsRepoFallback / fetchDeps set SourceURL — keeping URL-base disk harnesses strict;
  • or ratify the broader semantics: document at the AllowedRemoteResources field that omitting the key means "first-party defaults allowed" and explicit [] is the supported deny-all, and add a regression test for the config-present-key-omitted + URL-base case (there is currently none, which is how this shipped silently).

Two adjacent consistency gaps the squad also flagged if the broad behavior is kept:

  1. propagateDefaultAllowlist only checks == nil — good — but no test pins the explicit-[]-stays-deny-all contract; a future len()==0 refactor would silently flip deny-all to first-party-allow with every test green.
  2. The post-load check h.ValidateAllowedRemoteResources(orgCfg.AllowedRemoteResources) still uses the raw config value, so base scripts and URL skills see two different effective allowlists in one run (fails closed, but contradicts this helper's "consistent allowlist" doc comment).
Previous review comment (superseded)

The nil-only check is correct — please keep it that way despite the bot suggestion to also default len == 0. An explicitly empty allowed_remote_resources: [] means deny-all, and tryAgentsRepoFallback has the same contract (TestTryAgentsRepoFallback_ExplicitlyEmptyAllowlist). Defaulting the empty slice here would silently re-enable remote fetches for orgs that opted out.

One behavior change worth spelling out in this doc comment: this propagation is unconditional, not scoped to the fallback path. A config.yaml that exists but omits the allowed_remote_resources key previously meant "no remote resources anywhere in composition"; after this change it means "first-party defaults allowed". That's consistent with the policy #2947 already established (and it softens the #3172/#3173 outage class), but orgs that want deny-all must now write an explicit empty list — worth a sentence here so the next reader knows it's intentional.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Expanded the doc comment in a844d3f. Kept the nil-only check and added a sentence about the semantic change for configs omitting the key.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Went with option 1 in f251f09 — scoped the propagation inside the fetchDeps/SourceURL block so it only fires on the agents-repo fallback path. Config-present harnesses that omit allowed_remote_resources keep the deny-all behavior they had before.

Also added a test pinning the explicit-empty-slice contract ([]string{} stays empty, not replaced with defaults).

Point 2 (ValidateAllowedRemoteResources using the raw config value) — that's a pre-existing inconsistency unrelated to this fix. Worth a follow-up issue?

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.

Agreed — the ValidateAllowedRemoteResources inconsistency predates this PR and a follow-up issue is the right home for it; with the propagation now scoped it's back to a narrow, pre-existing wart rather than something this PR widens.

The scoping in f251f09 looks right to me, and it resolves the run/lock divergence I raised in the review body as well — with propagation limited to the fetch-deps path, both commands agree again on URL-base harnesses under a config that omits the key. The only residual there is pre-existing too (fullsend lock can't lock fallback-resolved agents at all since it only uses resolveHarnessPath), which could ride along in the same follow-up issue or its own.

With the explicit-empty test pinning the deny-all contract and the evals running for real on this PR now, this looks ready from my side once functional-tests completes.

Comment thread internal/cli/run_test.go
Comment on lines +3575 to +3577
// Override allowlist to match test server (default points at github.com).
opts.OrgAllowlist = []string{srv.URL + "/"}

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.

The propagateDefaultAllowlist(&opts) call here is immediately overwritten on the next line, so this test never verifies the property the bug was about: that the actual default list prefix-matches the script URLs the fallback constructs (https://raw.githubusercontent.com/fullsend-ai/agents/<sha>/scripts/...). The override is unavoidable for the httptest server, but consider adding one assertion before it:

propagateDefaultAllowlist(&opts)
assert.Equal(t, config.DefaultAllowedRemoteResources(), opts.OrgAllowlist)

plus a cheap static check that the default list covers the fallback's URL shape, e.g. assert.NotEmpty(t, harness.MatchingAllowedPrefixInList(defaultAgentsRepoURLPrefix+strings.Repeat("a", 40)+"/scripts/pre-triage.sh", config.DefaultAllowedRemoteResources())). Otherwise a future edit to DefaultAllowedRemoteResources() could regress #3396 with every test here still green.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added both assertions in a844d3f — the assert.Equal before the override, and the static MatchingAllowedPrefixInList check against the real defaultAgentsRepoURLPrefix.

Comment thread internal/cli/run.go Outdated
// Propagate the default allowlist when no config.yaml provided one.
// tryAgentsRepoFallback defaults internally, but that local default
// wasn't reaching LoadWithBase → resolveBaseScripts. See #3396.
propagateDefaultAllowlist(&composeOpts)

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.

Placement checks out: the orgCfg == nil strict-load block below can still overwrite OrgAllowlist with the real config's value for URL-base: harnesses, so the "URL base requires org-level allowed_remote_resources" self-authorization guard keeps working — the default only survives when there's genuinely no config-supplied allowlist.

Heads-up on the test plan though: the green functional-tests check on this PR is a path-filter skip, not a pass — the relevance filter only matches eval/, internal/scaffold/, .github/scripts/, and the workflow file, and this PR touches only internal/cli/. Same blind spot that let #2947 land without running the evals. The tidy way to close both gaps at once is to include fix (2) from #3396 in this PR: add internal/scaffold/fullsend-repo/config.yaml declaring allowed_remote_resources explicitly. That hardens CI's dogfooding path independently of this code fix and matches internal/scaffold/**, so the triage evals actually run against this branch before merge instead of us finding out on main again.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I looked into the functional-tests path filter (.github/workflows/functional-tests.yml:13-17) — it triggers on eval/**, internal/scaffold/**, .github/workflows/functional-tests.yml, and .github/scripts/**. Adding a scaffold config.yaml would only make the filter match PRs that touch that file, not PRs that touch internal/cli/. The real gap is that internal/cli/ isn't in the path filter at all — same blind spot for any future CLI change that affects agent resolution.

Do you think we should widen the filter to include internal/cli/**? That would be a separate commit, and it would make functional tests run on every CLI PR (which might be broader than intended). Or file a follow-up issue for the filter gap?

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.

Good call scoping it — and glad the workflow-file touch made the evals actually run on this PR (4/4 OK confirms the fix end-to-end).

On widening: I'd suggest keep internal/cli/run.go and add internal/harness/**, rather than all of internal/cli/**. Numbers from main since May 1: internal/cli/** would trigger the evals on ~18% of commits (472/2633), mostly flags/printers/unrelated subcommands — which largely defeats the point of having a filter. run.go + internal/harness/** lands at ~8% and covers both halves of this exact bug class: #3396 needed run.go (resolution didn't propagate the allowlist) and internal/harness/compose.go (composition enforced it) conspiring. A regression introduced purely in LoadWithBase/resolveBaseScripts — where the guard actually lives — wouldn't touch run.go at all, and internal/harness/ changes rarely enough (~2.5% of commits) that the added eval cost is small.

The more durable fix, though, is making this class not depend on LLM evals at all: a hermetic smoke test in the always-run test job that drives resolveAgentSourceLoadWithBase against an httptest server with no config.yaml present. Your new TestAgentsRepoFallback_LoadWithBase_NilAllowlist is already ~90% of that — it just calls the two stages separately instead of driving the real runAgent wiring, which is exactly where the propagation was missing. If that's a follow-up issue, the filter choice here becomes second-order; happy either way.

Expand propagateDefaultAllowlist doc comment to clarify the nil-vs-empty
semantic: nil means "no config supplied" and gets the default; an
explicit empty slice preserves deny-all.

Add two assertions to the integration test:
- Verify propagateDefaultAllowlist actually sets the default list before
  the test overrides it for the httptest server
- Static check that DefaultAllowedRemoteResources() prefix-matches the
  real agents-repo fallback URL shape, so a future edit to the default
  list can't silently regress #3396

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 9:54 PM UTC · Ended 9:55 PM UTC
Commit: e8381e3 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 9:55 PM UTC · Ended 9:56 PM UTC
Commit: e8381e3 · View workflow run →

The functional-tests path filter and relevance check only covered
eval/, internal/scaffold/, .github/scripts/, and the workflow file
itself. Changes to internal/cli/ (like the harness resolution fix in
this PR) skipped functional tests entirely — same blind spot that let
#2947 land without running the evals.

Add internal/cli/ to both the push.paths filter and the PR relevance
grep so that CLI changes affecting agent resolution get evaluated
before merge.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@ralphbean
ralphbean force-pushed the fix/3396-propagate-default-allowlist branch from f4940af to bb07ba8 Compare July 7, 2026 21:56
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:57 PM UTC · Completed 10:09 PM UTC
Commit: bb07ba8 · View workflow run →

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

Follow-up from a 4-agent review squad (Claude, Gemini, Codex reviewers) — one MEDIUM finding that can't be anchored inline because the file isn't in this diff:

[MEDIUM] fullsend lock doesn't get the propagation → run/lock divergence
internal/cli/lock.go:208lockOneAgent builds harness.ComposeOpts with the same nil-derived orgAllowlist pattern this PR fixes in runAgent, but without a propagateDefaultAllowlist call. Today lock never sets SourceURL (disk-only via resolveHarnessPath), so it can't reproduce #3396 itself — but for a URL-base: harness under a config.yaml that omits allowed_remote_resources, the two commands now disagree: run succeeds with the propagated first-party default while lock still hard-fails with "URL base requires org-level allowed_remote_resources". That breaks the lock-then-offline-run workflow for exactly the configs this PR aims to unblock. Suggest applying one policy at a single shared point (e.g. where orgAllowlist is derived from orgCfg, or at ComposeOpts construction) so run and lock can't drift — or at minimum a comment at lock.go's ComposeOpts noting the "no SourceURL here" invariant so a future extension revisits this.

The squad's remaining findings were already covered by the existing review comments (the propagateDefaultAllowlist scope/semantics comment has been updated in place with the consensus details, including the dual-allowlist inconsistency with h.ValidateAllowedRemoteResources and the missing explicit-empty-list test).

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review

Findings

Medium

Low

  • [scope-coherence] .github/workflows/functional-tests.yml:16 — The workflow trigger adds all of internal/cli/run.go (a 2700+ line file) to the push paths filter, but the bug fix only affects the tryAgentsRepoFallback / propagateDefaultAllowlist code path. Any future change to run.go — even unrelated refactors — will now trigger functional tests. Consider whether a comment documenting this breadth trade-off is warranted, or whether the functional test suite is fast enough that the extra triggers are acceptable.
Previous run

Review

Verdict: Approve

Summary

Clean, well-scoped bug fix for #3396. The PR correctly addresses the allowlist-default asymmetry introduced by PR #2947: tryAgentsRepoFallback defaulted OrgAllowlist internally for its own fetch but didn't propagate that default into composeOpts, causing LoadWithBase → resolveBaseScripts to reject pre_script/post_script URLs. The fix adds a small propagateDefaultAllowlist() function called between resolveAgentSource and LoadWithBase, with thorough unit and integration tests.

Security assessment: No security concerns. The fix does not create a fail-open condition — it aligns the behavior so the same defaults used for the initial agent fetch are also available during harness composition. The nil-vs-empty distinction is correctly handled: nil (no config) gets the default allowlist; explicitly empty []string{} preserves deny-all semantics. The subsequent org config load block (lines 277-286) correctly overrides the default when a real config is available.

Scope: The three commits are well-structured — the core fix, review feedback improvements, and the CI trigger update are properly separated with correct conventional commit types (fix and ci).

Findings

Two low-severity findings, neither blocking:

1. Missing test case for empty-slice edge case (internal/cli/run_test.go)

TestPropagateDefaultAllowlist covers nil → default and non-nil → preserved, but is missing a test for the explicitly-empty-slice ([]string{}) case. The function's doc comment specifically calls out that empty slices preserve deny-all semantics, and this nil-vs-empty distinction is the core correctness invariant. The codebase already tests this distinction for tryAgentsRepoFallback (TestTryAgentsRepoFallback_ExplicitlyEmptyAllowlist), establishing a pattern.

Suggested addition:

t.Run("explicitly empty allowlist preserved", func(t *testing.T) {
    empty := []string{}
    opts := harness.ComposeOpts{OrgAllowlist: empty}
    propagateDefaultAllowlist(&opts)
    assert.Equal(t, empty, opts.OrgAllowlist)
})

2. Ordering comment (internal/cli/run.go, line 274)

propagateDefaultAllowlist runs before the block at lines 277-286 that may load org config and overwrite composeOpts.OrgAllowlist. This override is intentional and correct — when a config is loaded, its allowlist takes precedence. A brief inline comment at the call site noting this ordering relationship would help future readers understand the design.

Dimensions reviewed

Dimension Result
Correctness ✅ Fix is logically sound; tests exercise the bug and the fix
Security ✅ No fail-open; nil-vs-empty correctly handled; allowlist scope is tight
Intent & coherence ✅ Traces to #3396; scope matches bug-fix authorization; commit types correct
Style & conventions ✅ Follows existing naming, doc-comment, and test patterns
Docs currency ✅ No documentation impact (internal implementation fix)
Cross-repo contracts ⏭ Skipped (no exported interfaces modified)

Protected paths detected — this PR modifies files under one or more
protected paths. The review agent cannot approve PRs that touch these paths.
A human reviewer must approve this PR.

Protected files in this PR:

  • .github/workflows/functional-tests.yml

Labels: PR fixes a harness composition bug and updates CI trigger paths

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment type/bug Confirmed defect in existing behavior component/harness Agent harness, config, and skills loading component/ci CI pipelines and checks labels Jul 7, 2026
Addresses review feedback: propagateDefaultAllowlist was called
unconditionally, which changed behavior for config-present harnesses
that omit allowed_remote_resources. A config.yaml that exists but
omits the key previously meant deny-all in LoadWithBase; the
unconditional propagation would have silently allowed first-party
URLs in that case.

Scope the call to inside the fetchDeps/SourceURL block so it only
fires when tryAgentsRepoFallback actually set SourceURL. This
preserves the deny-all contract for config-present harnesses with
URL bases.

Also add a test pinning the explicit-empty-slice-stays-deny-all
contract, so a future len==0 refactor can't silently flip it.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 10:13 PM UTC · Ended 10:27 PM UTC
Commit: e8381e3 · View workflow run →

@ralphbean

Copy link
Copy Markdown
Member Author

Re: the [MEDIUM] lock/run divergence — looked at it. lockOneAgent resolves via resolveHarnessPath (disk-only), never enters tryAgentsRepoFallback, never sets SourceURL. The divergence can't trigger today.

Since propagateDefaultAllowlist would be dead code in lock.go right now, I'd rather not add it. Filed #3429 to track the invariant so it gets revisited if lock ever grows a fallback path.

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

paths:
- 'eval/**'
- 'internal/scaffold/**'
- 'internal/cli/run.go'

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] scope-coherence

The workflow trigger adds all of internal/cli/run.go (a 2700+ line file) to the push paths filter, but the bug fix only affects the tryAgentsRepoFallback / propagateDefaultAllowlist code path. Any future change to run.go will now trigger functional tests, which is overly broad.

Suggested fix: Consider adding a comment explaining the breadth trade-off, or evaluate whether functional test runtime makes the extra triggers acceptable.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Jul 7, 2026
@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 7, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:13 PM UTC · Completed 10:27 PM UTC
Commit: f251f09 · View workflow run →

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

All review findings are addressed:

  • Root-cause fix verified: propagateDefaultAllowlist closes the exact #3396 asymmetry, now scoped to the fetch-deps/fallback path (f251f09) so config-present harnesses that omit allowed_remote_resources keep deny-all — which also resolves the run/lock divergence concern.
  • nil-vs-explicit-[] semantics documented and pinned by tests, including the new deny-all contract test.
  • Integration test now asserts the propagated default and statically verifies DefaultAllowedRemoteResources() covers the agents-repo fallback URL shape.
  • internal/cli/run.go added to the functional-tests relevance filter, and — decisively — the triage evals ran for real on this PR and pass 4/4 on the final head, proving the fix end-to-end against the exact failure from #3396.

Remaining items are pre-existing and agreed as follow-ups: the ValidateAllowedRemoteResources raw-config inconsistency, and fullsend lock not supporting fallback-resolved agents. Optionally consider widening the eval filter to internal/harness/** per the thread discussion, but that's not blocking.

@ralphbean
ralphbean added this pull request to the merge queue Jul 7, 2026
Merged via the queue into main with commit 0360038 Jul 7, 2026
30 of 31 checks passed
@ralphbean
ralphbean deleted the fix/3396-propagate-default-allowlist branch July 7, 2026 22:37
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jul 7, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 10:39 PM UTC · Completed 10:47 PM UTC
Commit: f251f09 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

PR #3425 fixed a regression from PR #2947 where tryAgentsRepoFallback defaulted the allowlist internally but didn't propagate it into composeOpts, causing LoadWithBase to reject script URLs. The fix required 4 commits over ~90 minutes. Three key findings: (1) the original bug in PR #2947 was reviewed by 3 bots and 3 humans across 10+ review cycles without catching the cross-function allowlist propagation gap; (2) on the fix PR, the human reviewer caught a HIGH-severity security scoping issue that the review agent missed entirely; (3) functional tests that would have caught the regression didn't run on PR #2947 because internal/cli/run.go wasn't in the CI path filter.

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/harness Agent harness, config, and skills loading requires-manual-review Review requires human judgment type/bug Confirmed defect in existing behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

functional-tests CI broken by PR #2947: agents-repo fallback allowlist default doesn't propagate to harness resource resolution

2 participants