Skip to content

feat(cli): add runtime fallback to agents repo for unconfigured agents#2947

Open
ggallen wants to merge 1 commit into
fullsend-ai:mainfrom
ggallen:worktree-runtime-fallback-agents-repo
Open

feat(cli): add runtime fallback to agents repo for unconfigured agents#2947
ggallen wants to merge 1 commit into
fullsend-ai:mainfrom
ggallen:worktree-runtime-fallback-agents-repo

Conversation

@ggallen

@ggallen ggallen commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

  • When an agent is not registered in config.yaml, resolveAgentSource now attempts to fetch the latest harness from fullsend-ai/agents before falling back to the scaffold-embedded harness on disk
  • The fallback resolves the main branch HEAD SHA via the GitHub API, constructs a pinned URL with integrity hash, and uses the existing FetchAgentHarness path for full security validation (allowlist, SHA256 integrity)
  • Only known first-party agents (triage, code, fix, review, retro, prioritize) are eligible for fallback; the fallback is skipped in offline mode or when no git token is available

This enables existing fullsend users to transparently use the extracted agents repository without any config changes, completing step 7 of the agent extraction plan.

Test plan

  • Existing TestResolveAgentSource_* tests pass with updated signature
  • New TestResolveAgentSource_AgentsRepoFallback_UnknownAgent — unknown agents skip fallback, fall to disk
  • New TestResolveAgentSource_AgentsRepoFallback_Offline — offline mode skips fallback
  • New TestResolveAgentSource_AgentsRepoFallback_NoToken — missing token skips fallback
  • New TestResolveAgentSource_AgentsRepoFallback_DiskWins — disk harness still works when no token
  • go vet ./... clean
  • go test ./internal/cli/ all pass
  • Manual: run fullsend run triage against a repo without agents: in config and verify it resolves from agents repo

🤖 Generated with Claude Code

@ggallen ggallen requested a review from a team as a code owner July 2, 2026 16:44
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:45 PM UTC · Completed 5:17 PM UTC
Commit: c3093c2 · View workflow run →

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

cli: fall back to fullsend-ai/agents for unconfigured agents

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add runtime resolution of first-party agents from fullsend-ai/agents when not configured.
• Pin fetched harnesses to a resolved commit SHA and SHA256 integrity hash.
• Extend test coverage for offline/no-token/unknown-agent fallback behavior.
Diagram

graph TD
  A["fullsend run"] --> B["resolveAgentSource()"] --> C{"Agent registered in config?"}
  C -- "yes" --> D["Resolve from config"] --> Z["Harness path"]
  C -- "no" --> E{"Agents repo fallback allowed?"} --> K["Disk harness lookup"] --> Z
  E -- "yes" --> F{{"GitHub API"}} --> G{{"raw.githubusercontent.com"}} --> H["Compute SHA256 + pin URL"] --> I["FetchAgentHarness()"] --> Z
  I --> J[("Local cache")]
  K --> L["Local harness file"]
  subgraph Legend
    direction LR
    _p["Process"] ~~~ _d{"Decision"} ~~~ _e{{"External"}} ~~~ _c[("Cache")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Pin to a release tag / version manifest (instead of main HEAD)
  • ➕ More deterministic and reproducible agent resolution
  • ➕ Simpler auditing/debugging when behavior changes
  • ➖ Requires release/tag management or a manifest publishing pipeline
  • ➖ May slow propagation of urgent harness fixes
2. Ship first-party harnesses embedded in the binary (go:embed)
  • ➕ Works offline and without git tokens
  • ➕ No external availability dependency at runtime
  • ➖ Larger binaries and slower iteration cadence (needs binary release)
  • ➖ Harder to patch harness content without redeploying
3. Require explicit config migration (no implicit fallback)
  • ➕ No surprises; configuration is the single source of truth
  • ➕ Avoids runtime network calls in previously-offline workflows
  • ➖ Breaks backward compatibility / adds migration friction
  • ➖ Undermines the goal of transparent agents repo extraction

Recommendation: The PR’s approach is a good compatibility bridge: it keeps config precedence, adds a guarded fallback only for known first-party agents, and reuses the existing FetchAgentHarness allowlist/integrity pipeline by pinning to commit SHA + SHA256. If long-term determinism becomes a concern, consider evolving the fallback from “main HEAD” to a tagged release or manifest-based pinning.

Files changed (2) +139 / -8

Enhancement (1) +85 / -3
run.goAdd agents repo fallback path to agent harness resolution +85/-3

Add agents repo fallback path to agent harness resolution

• Introduces a guarded fallback that fetches first-party agent harnesses from fullsend-ai/agents when the agent is not configured. Resolves main branch HEAD SHA via the GitHub API, pins the raw URL with a computed SHA256 integrity hash, and then routes through the existing FetchAgentHarness validation/caching path. Updates resolveAgentSource signature to accept a git token and wires it from runAgent.

internal/cli/run.go

Tests (1) +54 / -5
run_test.goExtend resolveAgentSource tests for agents repo fallback gating +54/-5

Extend resolveAgentSource tests for agents repo fallback gating

• Updates existing resolveAgentSource tests for the new gitToken argument. Adds new tests to ensure fallback is skipped for unknown agents, offline mode, and missing tokens, and to confirm disk harness resolution still works when fallback is unavailable.

internal/cli/run_test.go

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Site preview

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

Commit: ad3d8b672c3a4a341b4ec155bee48df861b52fbb

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.84848% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/cli/run.go 84.84% 5 Missing and 5 partials ⚠️

📢 Thoughts on this report? Let us know!

@qodo-code-review

qodo-code-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 54 rules

Grey Divider


Action required

1. Allowlist checked too late ✓ Resolved 🐞 Bug ⛨ Security
Description
tryAgentsRepoFallback fetches the harness content from raw.githubusercontent.com before verifying
the URL is permitted by allowed_remote_resources, so an org config can block use but cannot prevent
the outbound request. This undermines the intended policy boundary by allowing unexpected network
traffic even when the agents repo is not allowlisted.
Code

internal/cli/run.go[R2664-2687]

+	rawURL := agentsRepoURLPrefix + branchSHA + "/harness/" + agentName + ".yaml"
+
+	content, err := fetch.FetchURL(ctx, rawURL, composeOpts.FetchPolicy)
+	if err != nil {
+		return "", nil, false
+	}
+
+	hash := fetch.ComputeSHA256(content)
+	pinnedURL := rawURL + "#sha256=" + hash
+
+	allowlist := composeOpts.OrgAllowlist
+	if len(allowlist) == 0 {
+		allowlist = config.DefaultAllowedRemoteResources()
+	}
+
+	printer.StepStart(fmt.Sprintf("Fetching agent %s from %s/%s@%s", agentName, defaultAgentsRepoOwner, defaultAgentsRepoName, branchSHA[:12]))
+	localPath, dep, err := harness.FetchAgentHarness(ctx, pinnedURL, harness.ComposeOpts{
+		WorkspaceRoot: composeOpts.WorkspaceRoot,
+		FetchPolicy:   composeOpts.FetchPolicy,
+		AuditLogPath:  composeOpts.AuditLogPath,
+		OrgAllowlist:  allowlist,
+		TreeFetcher:   composeOpts.TreeFetcher,
+		GitToken:      composeOpts.GitToken,
+	})
Relevance

⭐⭐⭐ High

Security patterns favor fail-closed allowlist/SSRF enforcement; likely want allowlist check before
any outbound fetch.

PR-#1555
PR-#1096
PR-#792

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The fallback fetches remote content (FetchURL) before any allowed_remote_resources check is applied;
allowlist enforcement happens later inside FetchAgentHarness/fetchBaseURL, so the initial request
cannot be prevented by config allowlist.

internal/cli/run.go[2646-2687]
internal/harness/compose.go[323-337]
internal/harness/compose.go[348-357]

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

### Issue description
`tryAgentsRepoFallback` performs an initial network fetch (`fetch.FetchURL`) to compute the SHA256 integrity hash before it applies the org’s `allowed_remote_resources` checks (which occur inside `harness.FetchAgentHarness`). This means an org can configure `allowed_remote_resources` to *deny* the agents repo, but the process will still make an outbound request to `raw.githubusercontent.com`.

### Issue Context
- `harness.FetchAgentHarness` enforces `allowed_remote_resources` and integrity hashes.
- The fallback currently does a pre-fetch to compute the hash, and only later calls `FetchAgentHarness` (where allowlist enforcement happens).

### Fix Focus Areas
- internal/cli/run.go[2646-2687]
- internal/harness/compose.go[323-357]
- internal/harness/harness.go[866-870]

### Implementation notes
1. Compute the effective allowlist *before* calling `fetch.FetchURL`.
2. Before any network call, check that `rawURL` is allowlisted (e.g., with `harness.MatchingAllowedPrefixInList(rawURL, allowlist)`), and if not, return `("", nil, false)`.
3. Only proceed to `fetch.FetchURL` (hash computation) when the URL is allowed.
4. (Optional) log a `StepInfo/StepWarn` that fallback was skipped due to allowlist, to aid debugging.

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


2. tryAgentsRepoFallback uses gh.New ✓ Resolved 📘 Rule violation ⌂ Architecture
Description
The new agents-repo fallback constructs and uses a GitHub-specific client (internal/forge/github)
directly instead of routing the forge operation through the forge.Client abstraction. This couples
internal/cli to GitHub implementation details and violates the requirement that forge operations
flow through forge.Client.
Code

internal/cli/run.go[R2657-2659]

+	client := gh.New(gitToken)
+	branchSHA, err := client.GetBranchRef(ctx, defaultAgentsRepoOwner, defaultAgentsRepoName, defaultAgentsRepoBranch)
+	if err != nil {
Relevance

⭐⭐⭐ High

Recent PRs enforce forge-neutral CLI; GetBranchRef added to forge.Client and GitHub coupling removed
elsewhere.

PR-#2770
PR-#2736
PR-#2415

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 1062052 requires forge operations to be routed through the forge.Client interface.
The new fallback code in internal/cli/run.go constructs a GitHub-specific client with
gh.New(gitToken) and calls GetBranchRef on it directly, rather than receiving and using a
forge.Client instance.

Rule 1062052: Route all git forge operations through forge.Client
internal/cli/run.go[2657-2659]
internal/forge/forge.go[303-307]

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

## Issue description
`internal/cli/run.go` introduces new forge behavior (resolving the `main` branch HEAD SHA for `fullsend-ai/agents`) but does so by constructing a GitHub-specific client (`gh.New(...)`) and calling `GetBranchRef` on it directly. Compliance requires forge operations to go through the `forge.Client` interface so non-forge packages remain forge-agnostic.

## Issue Context
`forge.Client` already defines `GetBranchRef(...)`, so this fallback can use the interface without importing/depending on `internal/forge/github` directly.

## Fix Focus Areas
- internal/cli/run.go[2583-2694]

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



Remediation recommended

3. Unguarded SHA truncation ✓ Resolved 🐞 Bug ☼ Reliability
Description
tryAgentsRepoFallback slices branchSHA[:12] for logging without checking length, which can panic and
crash the CLI if GetBranchRef ever returns an empty/short SHA (e.g., unexpected API
response/decoding). This turns a non-fatal fallback path into a hard failure.
Code

internal/cli/run.go[R2679-2680]

+	printer.StepStart(fmt.Sprintf("Fetching agent %s from %s/%s@%s", agentName, defaultAgentsRepoOwner, defaultAgentsRepoName, branchSHA[:12]))
+	localPath, dep, err := harness.FetchAgentHarness(ctx, pinnedURL, harness.ComposeOpts{
Relevance

⭐⭐ Medium

General defensive-coding accepted, but no direct precedent for guarding SHA truncation in logs.

PR-#1059
PR-#764
PR-#1238

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code slices branchSHA[:12] after only checking the error return, and the GitHub client returns
the decoded SHA string without validating non-emptiness, so a short string would cause a runtime
panic.

internal/cli/run.go[2657-2680]
internal/forge/github/github.go[1302-1316]

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

### Issue description
`branchSHA[:12]` is used directly in a log message. If `branchSHA` is shorter than 12 characters, Go will panic with a slice-bounds error.

### Issue Context
`GetBranchRef` returns a string SHA parsed from JSON; it is not locally validated as non-empty/non-short before being truncated for display.

### Fix Focus Areas
- internal/cli/run.go[2657-2681]
- internal/forge/github/github.go[1302-1316]

### Implementation notes
- Add a small helper:
 - If `len(branchSHA) >= 12`, use `branchSHA[:12]`.
 - Else use `branchSHA` as-is (or treat empty as an error and skip fallback).
- Consider also validating `branchSHA != ""` and returning fallthrough (`false`) with a warning if empty.

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


Grey Divider

Qodo Logo

Comment thread internal/cli/run.go Outdated
Comment thread internal/cli/run.go Outdated
Comment thread internal/cli/run.go Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [integrity-bypass] internal/cli/run.go — The tryAgentsRepoFallback function computes a SHA256 hash post-fetch that serves only as a cache key and audit log entry, not for integrity verification. Unlike config-registered agents (where hashes are pinned at enrollment time), this fallback path has no known-good hash to compare against. Supply-chain integrity relies on the commit-pinned URL, TLS transport, and the org allowlist. This is a documented design trade-off for the transitional mechanism.

  • [fail-open] internal/cli/run.go — When composeOpts.OrgAllowlist is nil (no org config loaded), tryAgentsRepoFallback falls back to config.DefaultAllowedRemoteResources(). This means the agents-repo fallback activates for orgs without config. Mitigated: an explicitly empty allowlist ([]string{}) is not nil, so the default is NOT applied — this distinction is correctly tested in TestTryAgentsRepoFallback_ExplicitlyEmptyAllowlist.

  • [mutable-reference] internal/cli/run.go — The fallback resolves the main branch HEAD at runtime via GetBranchRef, meaning the content fetched can change between deployments without any binary update. A compromise of the fullsend-ai/agents repo's main branch could push malicious harness YAML automatically fetched by all installations using the fallback. Mitigated by defaultAgentsRepoKnownAgents gate (6 agents only), org allowlist, SHA validation, and branch protections.

  • [missing-authorization] internal/cli/run.go — This PR adds a non-trivial feature with no linked GitHub issue. The work is documented via a new extraction plan (docs/plans/agent-extraction-to-agents-repo.md), updates to ADR 0058, and architecture.md — establishing context, but a tracking issue would provide clearer authorization lineage.

Previous run

Review

Findings

High

  • [error-handling-gap] internal/cli/run.go:2717 — If CachePut fails on line 2717, the function logs a warning but continues to compute localPath via CachePath (line 2721) and returns it as the resolved harness path. CachePut is what writes the content file to disk at the CachePath-derived directory (confirmed by reading cache.go:101-123CachePut internally calls CachePath with the same content hash, then writes via atomicWrite). A failure means the file at localPath does not exist. The downstream caller will attempt to read this non-existent file and fail with an opaque error. The function should return false and fall through to disk-based lookup.
    Remediation: When CachePut fails, return ("", nil, false) to fall through to disk-based lookup, matching the function's documented contract that all errors are non-fatal.

Medium

  • [integrity-bypass] internal/cli/run.go:2715 — The tryAgentsRepoFallback function fetches YAML content via fetch.FetchURL, computes its SHA256 post-fetch (line 2715), but this hash serves no integrity purpose — it is only used as a cache key and audit log entry. Unlike config-registered agents where the hash is pinned at enrollment time and verified against fetched content, this fallback path has no known-good hash to verify against. Supply-chain integrity relies solely on the commit-pinned URL, TLS transport, and the org allowlist. The code comment on lines 2711–2714 explicitly acknowledges this limitation.
    Remediation: Consider embedding known-good SHA256 hashes for each agent in defaultAgentsRepoKnownAgents, or pin a specific commit SHA in the binary rather than resolving main at runtime.

  • [documentation-correctness] docs/plans/agent-registration.md:258 — The plan states the agents repo fallback "routes through FetchAgentHarness for integrity validation." This is factually incorrect. The implementation calls fetch.FetchURL directly (line 2706), manually caches content via fetch.CachePut (line 2717), and constructs the harness.Dependency struct itself (lines 2741–2748). It does not call FetchAgentHarness at all — that function is only used in the config-registered URL path (line 2637). This mismatch could mislead future reviewers into believing the fallback has the same integrity guarantees as config-registered agents.
    Remediation: Update the plan text to accurately describe the fallback's fetch mechanism: direct fetch.FetchURL with commit-pinned URL, manual caching, and no FetchAgentHarness call.

Low

  • [fail-open] internal/cli/run.go:2679 — When composeOpts.OrgAllowlist is nil (no org config loaded), tryAgentsRepoFallback falls back to config.DefaultAllowedRemoteResources(). This means the agents-repo fallback activates for orgs without config. Mitigated: an explicitly empty allowlist ([]string{}) is not nil, so the default is NOT applied — this distinction is correctly tested in TestTryAgentsRepoFallback_ExplicitlyEmptyAllowlist.

  • [mutable-reference] internal/cli/run.go:2683 — The fallback resolves the main branch HEAD at runtime via GetBranchRef, meaning the content fetched can change between deployments without any binary update. A compromise of the fullsend-ai/agents repo's main branch could push malicious harness YAML automatically fetched by all installations using the fallback. Mitigated by defaultAgentsRepoKnownAgents gate (6 agents only), org allowlist, SHA validation, and branch protections.

  • [missing-authorization] internal/cli/run.go — This PR adds a non-trivial feature with no linked GitHub issue. The work is documented via a new extraction plan (docs/plans/agent-extraction-to-agents-repo.md), updates to ADR 0058, and architecture.md — establishing context, but a tracking issue would provide clearer authorization lineage.

  • [scope-creep-beyond-authorization] internal/cli/run.go — The defaultAgentsRepoKnownAgents map hardcodes knowledge of the fullsend-ai/agents repository structure into the binary. ADR 0058's Context section identifies this pattern as the problem being solved. The code comments and ADR update now explicitly document this as a transitional mechanism with a planned deprecation path (Phase 5 / extraction plan Step 7).


Labels: PR adds agent resolution feature touching runner and harness components with substantial documentation updates.

Previous run (2)

Review

Findings

Medium

  • [integrity-bypass] internal/cli/run.go — The tryAgentsRepoFallback function fetches YAML content from a commit-pinned URL, computes its SHA256, then passes a self-referential pinnedURL (rawURL + "#sha256=" + hash) to FetchAgentHarness. The integrity check in fetchBaseURL compares fetched content against the expectedHash extracted from the URL fragment — but that hash was derived from the same content moments earlier, making the verification tautological. For config-registered agents, the hash is pinned at enrollment time, providing a meaningful integrity anchor. This fallback path lacks that anchor. If the fullsend-ai/agents repository is compromised, malicious harness YAML would be accepted. The code comment acknowledges this limitation and notes that supply-chain integrity relies on the commit-pinned URL, TLS transport, and the org allowlist rather than the hash.
    Remediation: Consider embedding known-good SHA256 hashes for each agent in the defaultAgentsRepoKnownAgents map (e.g., map[string]string with name → expected hash), or pin a specific commit SHA in the binary rather than resolving main at runtime.

Low

  • [fail-open] internal/cli/run.go — When composeOpts.OrgAllowlist is nil (no org config loaded), tryAgentsRepoFallback falls back to config.DefaultAllowedRemoteResources(). An org without config will still have agents resolved from the remote agents repo via this default allowlist. Mitigated: an explicitly empty AllowedRemoteResources ([]string{}) is not nil, so the default is NOT applied — this distinction is correctly tested in TestTryAgentsRepoFallback_ExplicitlyEmptyAllowlist.

  • [missing-authorization] internal/cli/run.go — This PR adds a non-trivial feature with no linked GitHub issue. The work is documented via a new extraction plan (docs/plans/agent-extraction-to-agents-repo.md), updates to ADR 0058, and architecture.md — establishing context, but a tracking issue would provide clearer authorization lineage.

  • [scope-creep-beyond-authorization] internal/cli/run.go — The defaultAgentsRepoKnownAgents map hardcodes knowledge of the fullsend-ai/agents repository structure into the binary. ADR 0058's Context section identifies this pattern ("which agents fullsend knows about is currently compiled into the binary") as the problem being solved. The code comments and ADR update now explicitly document this as a transitional mechanism with a planned deprecation path (ADR 0058 Phase 5 / extraction plan Step 7).

Previous run (3)

Review

Findings

Medium

  • [integrity-bypass] internal/cli/run.go — The tryAgentsRepoFallback function fetches YAML content from a commit-pinned URL, computes its SHA256, then passes a self-referential pinnedURL (rawURL + "#sha256=" + hash) to FetchAgentHarness. The integrity check in fetchBaseURL compares fetched content against the expectedHash extracted from the URL fragment — but that hash was derived from the same content moments earlier, making the verification tautological. For config-registered agents, the hash is pinned at enrollment time, providing a meaningful integrity anchor. This fallback path lacks that anchor. If the fullsend-ai/agents repository is compromised, malicious harness YAML would be accepted. The code comment acknowledges this limitation and notes that supply-chain integrity relies on the commit-pinned URL, TLS transport, and the org allowlist rather than the hash.
    Remediation: Consider embedding known-good SHA256 hashes for each agent in the defaultAgentsRepoKnownAgents map (e.g., map[string]string with name → expected hash), or pin a specific commit SHA in the binary rather than resolving main at runtime.

Low

  • [fail-open] internal/cli/run.go — When composeOpts.OrgAllowlist is nil (no org config loaded), tryAgentsRepoFallback falls back to config.DefaultAllowedRemoteResources(). An org without config will still have agents resolved from the remote agents repo via this default allowlist. Mitigated: an explicitly empty AllowedRemoteResources ([]string{}) is not nil, so the default is NOT applied — this distinction is correctly tested in TestTryAgentsRepoFallback_ExplicitlyEmptyAllowlist.

  • [missing-authorization] internal/cli/run.go — This PR adds a non-trivial feature with no linked GitHub issue. The work is documented via a new extraction plan (docs/plans/agent-extraction-to-agents-repo.md), updates to ADR 0058, and architecture.md — establishing context, but a tracking issue would provide clearer authorization lineage.

  • [scope-creep-beyond-authorization] internal/cli/run.go — The defaultAgentsRepoKnownAgents map hardcodes knowledge of the fullsend-ai/agents repository structure into the binary. ADR 0058's Context section identifies this pattern ("which agents fullsend knows about is currently compiled into the binary") as the problem being solved. The code comments and ADR update now explicitly document this as a transitional mechanism with a planned deprecation path (ADR 0058 Phase 5 / extraction plan Step 7).


Labels: PR modifies agent harness resolution logic in the CLI runner and adds documentation for the agent extraction plan.

Previous run (4)

Review

Findings

High

  • [stale-behavioral-description] docs/architecture.md — The Agent Registry section describes agent resolution as config-based with runtime URL/path loading, but does not document the new three-tier resolution model (config → agents repo fallback → disk harnesses) introduced by this PR. This is the primary architectural reference and is now materially inaccurate about a core mechanism.
    Remediation: Update the Agent Registry section to document the three-tier resolution model: (1) config entries from OrgConfig.Agents, (2) fallback to fullsend-ai/agents repo for known agents not in config, (3) fallback to disk harnesses.

Medium

  • [integrity-bypass] internal/cli/run.go — The tryAgentsRepoFallback function fetches remote YAML content, computes its SHA256, then passes a self-referential pinnedURL (rawURL + "#sha256=" + hash) to FetchAgentHarness. The integrity verification inside fetchBaseURL is tautological — it verifies content against a hash derived from that same content. If the upstream agents repo is compromised, the fallback will accept the malicious harness with no integrity check against a known-good hash. Config-registered agents are protected because their hashes are pinned at enrollment time; this fallback path lacks equivalent protection.
    Remediation: Consider embedding known-good SHA256 hashes for each agent harness in the agentsRepoKnownAgents map, or pin a specific commit SHA in the binary rather than resolving main at runtime.

  • [missing-authorization] internal/cli/run.go — This PR adds a runtime fallback mechanism to fetch agents from fullsend-ai/agents when not found in config, with no linked issue. The PR body references "step 7 of the agent extraction plan" but the documented agent-registration.md plan has 5 phases with no step 7.
    Remediation: Link the issue or plan document that authorizes this transitional fallback mechanism.

  • [scope-design-mismatch] internal/cli/run.go — The diff introduces a third agent resolution layer (config → fullsend-ai/agents repo fallback → scaffold) not described in ADR-0058. ADR-0058 defines agent registration as a config-level concept with resolution from merged set of scaffold + config, then lookup by name. The agents repo fallback tier is not documented.
    Remediation: Update ADR-0058 or write a superseding ADR documenting this transitional fallback mechanism.

  • [scope-creep-beyond-authorization] internal/cli/run.go — The new agentsRepoKnownAgents map hardcodes knowledge of the fullsend-ai/agents repository structure into the binary. ADR-0058's Context section explicitly identifies this pattern ("which agents fullsend knows about is currently compiled into the binary") as the problem being solved. If this is an intentional transitional aid, it must be documented as such with a planned deprecation path.
    Remediation: Document this as a transitional mechanism with a planned deprecation path.

  • [missing-behavioral-change] docs/plans/agent-registration.md — Phase 3 implementation plan describes agent resolution as a two-tier model (config → disk scaffold fallback). The new tryAgentsRepoFallback and agents repo resolution tier are not documented in this plan.
    Remediation: Update Phase 3 sections to document the three-tier resolution.

Low

  • [fail-open] internal/cli/run.go — When composeOpts.OrgAllowlist is nil (no org config loaded), tryAgentsRepoFallback falls back to config.DefaultAllowedRemoteResources(). An org that deliberately removes the agents repo prefix from their AllowedRemoteResources would still see the fallback execute when orgCfg is nil, bypassing their intent to restrict remote resources.

  • [input-validation] internal/cli/run.go — The branchSHA returned by forgeClient.GetBranchRef is interpolated directly into the URL without validation that it is a valid hex SHA. URL normalization in MatchingAllowedPrefixInList mitigates path traversal, but explicit validation (e.g., ^[0-9a-f]{40}$) would be cleaner defense-in-depth.

  • [implementation-architecture-gap] internal/cli/run.go — The tryAgentsRepoFallback pre-fetches content and caches it before calling FetchAgentHarness, which does call the shared infrastructure. The duplication is limited to SHA resolution and content pre-fetch needed to construct the pinned URL.

  • [redundant-network-call] internal/cli/run.gotryAgentsRepoFallback fetches harness content via fetch.FetchURL, computes SHA256, caches with CachePut, then calls FetchAgentHarness which hits the cache. The double-fetch is mitigated by cache but there is still redundant parsing/processing.

  • [naming-consistency] internal/cli/run.go — Inconsistent naming: defaultAgentsRepoOwner/Name/Branch use default prefix but agentsRepoURLPrefix and agentsRepoKnownAgents drop it.

Previous run (5)

Review

Findings

High

  • [stale-behavioral-description] docs/architecture.md — The Agent Registry section describes agent resolution as config-based with runtime URL/path loading, but does not document the new three-tier resolution model (config → agents repo fallback → disk harnesses) introduced by this PR. This is the primary architectural reference and is now materially inaccurate about a core mechanism.
    Remediation: Update the Agent Registry section to document the three-tier resolution model: (1) config entries from OrgConfig.Agents, (2) fallback to fullsend-ai/agents repo for known agents not in config, (3) fallback to disk harnesses.

Medium

  • [integrity-bypass] internal/cli/run.go — The tryAgentsRepoFallback function fetches remote YAML content, computes its SHA256, then passes a self-referential pinnedURL (rawURL + "#sha256=" + hash) to FetchAgentHarness. The integrity verification inside fetchBaseURL is tautological — it verifies content against a hash derived from that same content. If the upstream agents repo is compromised, the fallback will accept the malicious harness with no integrity check against a known-good hash. Config-registered agents are protected because their hashes are pinned at enrollment time; this fallback path lacks equivalent protection.
    Remediation: Consider embedding known-good SHA256 hashes for each agent harness in the agentsRepoKnownAgents map, or pin a specific commit SHA in the binary rather than resolving main at runtime.

  • [missing-authorization] internal/cli/run.go — This PR adds a runtime fallback mechanism to fetch agents from fullsend-ai/agents when not found in config, with no linked issue. The PR body references "step 7 of the agent extraction plan" but the documented agent-registration.md plan has 5 phases with no step 7.
    Remediation: Link the issue or plan document that authorizes this transitional fallback mechanism.

  • [scope-design-mismatch] internal/cli/run.go — The diff introduces a third agent resolution layer (config → fullsend-ai/agents repo fallback → scaffold) not described in ADR-0058. ADR-0058 defines agent registration as a config-level concept with resolution from merged set of scaffold + config, then lookup by name. The agents repo fallback tier is not documented.
    Remediation: Update ADR-0058 or write a superseding ADR documenting this transitional fallback mechanism.

  • [scope-creep-beyond-authorization] internal/cli/run.go — The new agentsRepoKnownAgents map hardcodes knowledge of the fullsend-ai/agents repository structure into the binary. ADR-0058's Context section explicitly identifies this pattern ("which agents fullsend knows about is currently compiled into the binary") as the problem being solved. If this is an intentional transitional aid, it must be documented as such with a planned deprecation path.
    Remediation: Document this as a transitional mechanism with a planned deprecation path.

  • [missing-behavioral-change] docs/plans/agent-registration.md — Phase 3 implementation plan describes agent resolution as a two-tier model (config → disk scaffold fallback). The new tryAgentsRepoFallback and agents repo resolution tier are not documented in this plan.
    Remediation: Update Phase 3 sections to document the three-tier resolution.

Low

  • [fail-open] internal/cli/run.go — When composeOpts.OrgAllowlist is nil (no org config loaded), tryAgentsRepoFallback falls back to config.DefaultAllowedRemoteResources(). An org that deliberately removes the agents repo prefix from their AllowedRemoteResources would still see the fallback execute when orgCfg is nil, bypassing their intent to restrict remote resources.

  • [input-validation] internal/cli/run.go — The branchSHA returned by forgeClient.GetBranchRef is interpolated directly into the URL without validation that it is a valid hex SHA. URL normalization in MatchingAllowedPrefixInList mitigates path traversal, but explicit validation (e.g., ^[0-9a-f]{40}$) would be cleaner defense-in-depth.

  • [implementation-architecture-gap] internal/cli/run.go — The tryAgentsRepoFallback pre-fetches content and caches it before calling FetchAgentHarness, which does call the shared infrastructure. The duplication is limited to SHA resolution and content pre-fetch needed to construct the pinned URL.

  • [redundant-network-call] internal/cli/run.gotryAgentsRepoFallback fetches harness content via fetch.FetchURL, computes SHA256, caches with CachePut, then calls FetchAgentHarness which hits the cache. The double-fetch is mitigated by cache but there is still redundant parsing/processing.

  • [naming-consistency] internal/cli/run.go — Inconsistent naming: defaultAgentsRepoOwner/Name/Branch use default prefix but agentsRepoURLPrefix and agentsRepoKnownAgents drop it.

Previous run (6)

Review

Findings

Medium

  • [logic-error] internal/cli/run.go — In tryAgentsRepoFallback, the agent name is normalized to lowercase for the known-agents check (strings.ToLower(agentName)) but used as-is when constructing the raw GitHub URL. GitHub raw content URLs are case-sensitive, so fullsend run Triage would pass the allowlist gate but construct .../harness/Triage.yaml instead of .../harness/triage.yaml, causing a silent 404 and falling through to disk. Use strings.ToLower(agentName) in the URL construction as well.

  • [test-inadequate] internal/cli/run_test.goTestResolveAgentSource_AgentsRepoFallback_DiskWins passes an empty git token, which causes tryAgentsRepoFallback to bail immediately (no-token guard). The test does not actually verify that disk "wins" over a successful agents-repo fetch — it only tests disk resolution when the fallback is skipped entirely. There is no test covering the interaction when both sources are available with a valid token.

  • [scope-design-mismatch] internal/cli/run.gotryAgentsRepoFallback adds a third resolution layer (config → agents repo → disk) not described in ADR-0058, which defines only config and scaffold/disk layers. Consider updating ADR-0058 or writing a superseding ADR to document this transitional fallback mechanism.

  • [scope-creep-beyond-authorization] internal/cli/run.go — The new agentsRepoKnownAgents map and defaultAgentsRepo* constants hardcode knowledge of the fullsend-ai/agents repository structure into the binary. ADR-0058's Context section identifies this exact pattern ("which agents fullsend knows about is currently compiled into the binary") as the problem being solved. If this is an intentional transitional aid, document it as such with a planned deprecation path.

Low

  • [redundant-network-call] internal/cli/run.go — The harness content is fetched via fetch.FetchURL to compute the SHA256 hash, then FetchAgentHarness fetches the same commit-SHA-pinned URL again. Consider caching the first fetch result or passing the content through to avoid the double round-trip.

  • [fail-open] internal/cli/run.go — When composeOpts.OrgAllowlist is empty, the code substitutes config.DefaultAllowedRemoteResources(). An org that explicitly sets allowed_remote_resources: [] to prohibit remote fetches would have the default silently applied. Practical risk is limited (defaults only allow fullsend-ai-owned URLs).

  • [edge-case] internal/cli/run.gobranchSHA[:12] in the StepStart message does not guard against an unexpectedly short SHA. In practice GetBranchRef always returns a 40-char hex string, but a defensive min(len(branchSHA), 12) truncation would prevent a theoretical panic.

  • [comment-style] internal/cli/run.go — Const block comment and agentsRepoKnownAgents comment are slightly verbose compared to the established per-constant naming pattern (e.g., metricsFile is the filename...).

  • [comment-accuracy] internal/cli/run.go — The resolveAgentSource comment could clarify that the agents-repo fallback only applies to unconfigured agents, not all agents.

  • [error-handling-idiom] internal/cli/run.go — Minor: "Could not resolve" vs "Failed to" — both forms are used equally across the codebase, so this is a stylistic nit only.


Labels: PR modifies agent harness resolution logic in the CLI runner.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment component/harness Agent harness, config, and skills loading component/runner Agent runner behavior and lifecycle labels Jul 2, 2026
@ggallen ggallen force-pushed the worktree-runtime-fallback-agents-repo branch from c3093c2 to 5965f00 Compare July 2, 2026 17:35
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:36 PM UTC · Completed 5:53 PM UTC
Commit: 5965f00 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Jul 2, 2026
@ggallen ggallen force-pushed the worktree-runtime-fallback-agents-repo branch from 5965f00 to 2d3468a Compare July 2, 2026 18:00
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:01 PM UTC · Completed 6:17 PM UTC
Commit: 2d3468a · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot dismissed their stale review July 2, 2026 18:16

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 2, 2026
@ggallen ggallen force-pushed the worktree-runtime-fallback-agents-repo branch from 2d3468a to 6626d23 Compare July 2, 2026 18:26
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:27 PM UTC · Completed 6:41 PM UTC
Commit: 6626d23 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added feature Feature-category issue awaiting human prioritization component/docs User-facing documentation and removed requires-manual-review Review requires human judgment labels Jul 2, 2026
When an agent is not registered in config.yaml, resolveAgentSource now
attempts to fetch the latest harness from fullsend-ai/agents before
falling back to the scaffold-embedded harness on disk.

The fallback resolves the main branch HEAD SHA via the forge.Client
interface, validates the SHA format, checks the org allowlist before
any outbound fetch, and caches the fetched content directly. Supply-
chain integrity relies on commit-pinned URLs, TLS transport, and the
org allowlist.

Only known first-party agents (triage, code, fix, review, retro,
prioritize) are eligible for fallback; the fallback is skipped in
offline mode or when no forge client is available. This is a
transitional mechanism for the agent extraction
(docs/plans/agent-extraction-to-agents-repo.md) and will be removed
once all users have migrated to config-driven registration (ADR 0058
Phase 5).

Signed-off-by: Greg Allen <gallen@redhat.com>
Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
@ggallen ggallen force-pushed the worktree-runtime-fallback-agents-repo branch from 6626d23 to ad3d8b6 Compare July 2, 2026 18:53
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:54 PM UTC · Completed 7:08 PM UTC
Commit: ad3d8b6 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot dismissed their stale review July 2, 2026 19:08

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Jul 2, 2026
Comment thread internal/cli/run.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/docs User-facing documentation component/harness Agent harness, config, and skills loading component/runner Agent runner behavior and lifecycle feature Feature-category issue awaiting human prioritization ready-for-merge All reviewers approved — ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants