Skip to content

fix(#6971): allow claude.exe and pi on scaffold Vertex profile, warn on profile shadowing - #6975

Merged
waynesun09 merged 3 commits into
mainfrom
agent/6971-scaffold-vertex-binary-allowlist
Sep 3, 2026
Merged

fix(#6971): allow claude.exe and pi on scaffold Vertex profile, warn on profile shadowing#6975
waynesun09 merged 3 commits into
mainfrom
agent/6971-scaffold-vertex-binary-allowlist

Conversation

@fullsend-ai-coder

@fullsend-ai-coder fullsend-ai-coder Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Every Claude run on the v0.40.0 sandbox image (Claude Code 2.1.258) that loads the Vertex profile shipped in this repo fails on its first call with API Error: Error code policy_denied, 0 tokens. The gateway log shows why: OpenShell's OPA denies /usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe -> sts.googleapis.com:443 because the scaffold profile only allowlists **/claude. Claude Code 2.1.2xx installs its native binary at bin/claude.exe even on Linux, and since #6647 that pinned install is the binary that runs in the sandbox. The fleet copy in fullsend-ai/agents was fixed by fullsend-ai/agents#1118; this repo's embedded copy, which functional-tests and local runs load through --fullsend-dir, was not. See #6971 for the full analysis.

Three commits:

  1. fix(scaffold): allow claude.exe and pi on the Vertex profile — adds **/claude.exe and **/pi to internal/scaffold/fullsend-repo/profiles/fullsend-vertex-ai.yaml so the list matches the agents copy (**/pi is carried for parity; pi itself runs via node), pins the whole list in TestScaffoldVertexProfile_BinaryAllowlist, and updates the bring-your-own-agent guide and the runtime egress diagram that still showed the old list.

  2. fix(run): warn when a profiles/ directory copy shadows a harness-resolved profilefullsend run imports harness-resolved profiles first and then <fullsend-dir>/profiles/, which deletes and re-imports every id it finds, so a stale directory copy silently undid the fixed profile the functional run had just fetched (functional-tests: every triage case fails with API Error policy_denied on main since 2026-09-03 #6962). The runner now warns per shadowed id, naming the directory and the harness copy. Warning only, no precedence change; a local-path profile that already lives in profiles/ and the runner-generated fullsend-gitlab-forge profile are not reported.

  3. test(profiles): pin the egress binary each runtime needs on its profile — the same class of gap covered for every runtime. OpenShell's OPA matches a binaries: glob against the connecting process's /proc/<pid>/exe or any ancestor's, which splits runtimes in two: pi and codex are wrapped by node (**/node admits them through the ancestor; **/codex also names codex's own vendor/<triple>/bin/codex, spawned by bin/codex.js), while Claude Code is exec'd directly through a symlink to bin/claude.exe and so has no wrapper ancestor to fall back on. That is why sandbox: scaffold Vertex profile lacks **/claude.exe, so every 0.40.0-image Claude run fails with policy_denied #6971 could happen at all, and it is the discriminator for which runtimes a pin bump can break. TestScaffoldProfilesAllowRuntimeBinaries walks config.ValidRuntimes() and fails for any selectable runtime with no declared mapping, or whose profiles lack its globs. OpenCode is exec'd directly too (opencode-ai ships a stub bin/opencode.exe that postinstall.mjs replaces with opencode-linux-x64/bin/opencode), but it is still a stub, not in the sandbox image, and which of the two paths gets exec'd is a Containerfile decision nobody has made — so no globs are guessed at now: the note lives on OpenCodeRuntime, and the test fails the day opencode joins ValidRuntimes until a mapping exists. Negative check: removing **/claude.exe from the scaffold profile fails the test with profile fullsend-vertex-ai must allowlist **/claude.exe for the claude runtime.

Docs, written as walkthroughs rather than rationale dumps: the bring-your-own-agent profile snippet keeps a one-line comment and gains a Troubleshooting row keyed on the literal API Error: Error code policy_denied with the grep DENIED command; running-agents-locally.md gets the same symptom with the log line to look for and the profiles/ directory trap; customizing-agents.md shows the actual warning text instead of describing it; the contributor-facing mechanism moved to an "Egress binary identity per runtime" section plus a checklist step and a CODEX_VERSION re-check row.

Verification

  • functional-tests on this PR (run 33781026441, first head): CASES: 4 (4 OK, 0 FAIL) on Agent: claude-opus-4-6 (v2.1.258), image fullsend-sandbox@sha256:f8255971…, non-zero cost per case, no DENIED … claude.exe lines. This is the first live confirmation that OpenShell's glob.match accepts **/claude.exe.
  • go test ./internal/scaffold/... ./internal/cli/..., go vet, make lint (pre-commit incl. gitlint) pass locally on e17077233.
  • Review: two rounds with Claude (researcher) and Grok reviewers on the initial bot commit and on the fix diff; all MEDIUM+ findings addressed (false-positive shadow warning for same-directory local-path profiles and the generated GitLab profile, warning wording that overstated a precedence the two import caches do not guarantee, stale docs).

Follow-ups filed

Closes #6971
Fixes #6962

@fullsend-ai-coder
fullsend-ai-coder Bot requested a review from a team as a code owner September 3, 2026 16:50
@fullsend-ai-coder fullsend-ai-coder Bot added the ready-for-review Triggers review agent dispatch label Sep 3, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Ended 4:52 PM UTC

Commit: c7794f6 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:53 PM UTC · Completed 5:12 PM UTC

Commit: c7794f6 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.28

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Tier 1 is very low (1.4) due to minimal scope, zero protected/security paths, and bot authorship; Tier 2 is elevated (2.5) because core files (run.go, run_test.go) have high churn with many authors and frequent fix commits; Tier 3 is low (1.3) with perfect issue-to-PR alignment, clear acceptance criteria coverage, and safe non-breaking changes (allowlist entries and warning-only message); composite weighted score 2 (moderate) preserved from prior assessment.

Previous run

Risk Assessment: moderate (2/5)

Details

Small, well-tested bug fix (97 lines, 50% test ratio) adding allowlist entries and a warning message; Tier 1 is very low (1.1) due to minimal scope and no sensitive paths, but Tier 2 is elevated (3.3) because run.go is a high-churn core file with many recent authors and frequent fix commits; Tier 3 is low (1.5) with clear issue-to-PR scope alignment and all acceptance criteria met.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [test-adequacy] internal/cli/runtime_binaries_test.go:21runtimeEgressBinaries lists **/node as the only required glob for the pi runtime against fullsend-vertex-ai, though the profile also contains **/pi. The test checks superset inclusion (profile contains test entries) not equality, so removing **/pi from the profile would not be caught by this test. However, TestScaffoldVertexProfile_BinaryAllowlist uses ElementsMatch and pins the exact binary set, covering this gap.

  • [permission-manifest] internal/scaffold/fullsend-repo/profiles/fullsend-vertex-ai.yaml:24 — Adding **/pi to the Vertex AI egress profile grants the pi agent binary network access through the OpenShell gateway to api.anthropic.com:443 and *.googleapis.com:443 with allow_uninspected_credentials: true and access: read-write. The access grant is operationally justified for the pi Vertex AI runtime. The read-write access scope is pre-existing for **/claude and **/node and is not introduced by this PR.

Previous run

Review

Findings

Low

  • [test-adequacy] internal/scaffold/scaffold_test.go:1024TestScaffoldVertexProfile_BinaryAllowlist asserts the presence of **/claude and **/claude.exe but does not assert **/pi, which is also added by this PR. If **/pi were accidentally removed from the scaffold profile in a future change, this test would not catch the regression.
    Remediation: Add assert.Contains(t, profile.Binaries, "**/pi", "scaffold Vertex profile must allowlist **/pi") to the test.

  • [naming-consistency] internal/cli/run.go:1291 — The warning message uses the verb "replaces" and the qualifier "harness-resolved", but the established pattern in this file is "shadows" and "URL-resolved". The provider shadow warning at line 1314 reads Local provider %q shadows URL-resolved provider of the same name. The new function is itself named shadowedProfileIDs, making the verb mismatch an internal inconsistency within the same PR.
    Remediation: Reword to match the provider warning: printer.StepWarn(fmt.Sprintf("Profile %q in %s shadows URL-resolved profile of the same id", id, profilesDir))

  • [stale-doc] docs/guides/user/bring-your-own-agent.md:133 — The inline example of profiles/fullsend-vertex-ai.yaml shows only **/claude and **/node in the binaries list. The scaffold profile now also includes **/claude.exe and **/pi. The doc explicitly tells users to "Copy this from the scaffold", so the example should match what the scaffold actually produces.
    Remediation: Add **/claude.exe after **/claude and **/pi after **/node in the binaries list shown at lines 133-135.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ❌ Failure (post-script /home/runner/work/fullsend/fullsend/.fullsend/.fullsend-cache/resources/sha256/21644e0238d0b83db763fa138c412322223b0ba4a919a542b839e11b6a3d62e7/scripts/post-fix.sh failed: exit status 1) · Started 5:14 PM UTC · Completed 5:22 PM UTC

Commit: c7794f6 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $1.19

@waynesun09
waynesun09 force-pushed the agent/6971-scaffold-vertex-binary-allowlist branch 2 times, most recently from 701718b to e170772 Compare September 3, 2026 17:18
@waynesun09

Copy link
Copy Markdown
Member

Taken over from the bot commit: rebuilt as two conventional commits (9c1ce0a44 scaffold profile + docs, e17077233 shadow warning) after two review rounds; details and the functional-tests evidence are in the PR body. Follow-ups: #6977, #6978.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Ended 5:19 PM UTC

Commit: 701718b · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:21 PM UTC · Ended 5:40 PM UTC

Commit: e170772 · View workflow run →

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

⚠️ Post-fix script failed — Push rejected (exit code 1)

The fix agent completed, but the post-fix script failed before finishing.

Workflow run: https://github.com/fullsend-ai/fullsend/actions/runs/33783187471

Details:
To https://github.com/fullsend-ai/fullsend.git
! [rejected] agent/6971-scaffold-vertex-binary-allowlist -> agent/6971-scaffold-vertex-binary-allowlist (fetch first)
error: failed to push some refs to 'https://github.com/fullsend-ai/fullsend.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
To https://github.com/fullsend-ai/fullsend.git
! [rejected] agent/6971-scaffold-vertex-binary-allowlist -> agent/6971-scaffold-vertex-binary-allowlist (stale info)
error: failed to push some refs to 'https://github.com/fullsend-ai/fullsend.git'
Please check the workflow logs for full details and retry with /fs-fix if appropriate.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Site preview

Preview: https://7943f6a0-site.fullsend-ai.workers.dev

Commit: e75bc411d84078f7ea84e477095ef9eaf64472eb

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:42 PM UTC · Ended 5:57 PM UTC

Commit: 5fd4c54 · View workflow run →

Claude Code 2.1.2xx installs its native binary at bin/claude.exe
even on Linux, and since #6647 the CLAUDE_CODE_VERSION-pinned install
is the binary that runs in the sandbox. The scaffold Vertex profile
only allowlisted **/claude, so OpenShell's OPA denied claude.exe the
STS call and every Claude run on the 0.40.0 image failed on its first
request with "API Error: Error code policy_denied" (0 tokens). The
fleet copy in fullsend-ai/agents was fixed by fullsend-ai/agents#1118;
this repo's embedded copy, which functional-tests and local runs load
through --fullsend-dir, was not.

Add **/claude.exe and **/pi so the binaries list matches the agents
copy (**/pi is carried for parity with that copy; pi itself runs via
node), pin the whole list in a scaffold test so the two copies cannot
drift on this again, and update the bring-your-own-agent guide and the
runtime egress diagram that still showed the old list.

Refs #6971

Assisted-by: Claude (code, fix, review), Grok (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
…lved profile

fullsend run imports the harness-resolved profiles first and then
imports <fullsend-dir>/profiles/, which deletes and re-imports every id
it finds, so a directory copy silently replaces a profile the harness
already carries. That is how the functional tests kept failing with
policy_denied after the fixed Vertex profile was fetched from
fullsend-ai/agents (#6962): the stale scaffold copy won without a
trace.

Emit a warning per shadowed id naming both copies. The override stays
in place (per-repo customization relies on it); a local-path profile
that already lives in profiles/ is the same file and is not reported,
and duplicate ids in the directory are reported once. Document the
precedence next to the provider rule in customizing-agents.md.

Closes #6971

Assisted-by: Claude (code, fix, review), Grok (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
The claude.exe gap (#6971) was a class of failure, not a one-off: the
gateway's OPA matches a profile's binaries: glob against the
kernel-resolved exe of the process that opens the connection, so every
runtime has to name the file that actually runs, and a runtime pin bump
or a new runtime can silently break that. Cover the other runtimes the
same way:

- codex: bin/codex.js spawns vendor/<triple>/bin/codex (verified with
  npm pack --dry-run on the 0.152.1 pin), matched by the **/codex the
  OpenAI profile already carries.
- pi: an npm package executed by node, matched by **/node.
- opencode: follows the claude.exe pattern (opencode-ai ships a stub
  bin/opencode.exe that postinstall replaces with the platform binary,
  opencode-linux-x64/bin/opencode). The runtime is still a stub and not
  in the sandbox image, so nothing is added to the profiles; the
  mapping is pre-declared so the test fails the moment opencode joins
  config.ValidRuntimes until both inference profiles allow
  **/opencode and **/opencode.exe.

TestScaffoldProfilesAllowRuntimeBinaries walks config.ValidRuntimes and
requires each selectable runtime to have a declared mapping whose globs
are present in the scaffold profiles it uses; the runtime matrix in
docs/contributing/runtime-implementation.md gains an "egress binary
identity" row with the same facts and the verification recipe.

Refs #6971

Assisted-by: Claude (code, review), Grok (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@waynesun09
waynesun09 force-pushed the agent/6971-scaffold-vertex-binary-allowlist branch from 5fd4c54 to e75bc41 Compare September 3, 2026 17:55
@waynesun09

Copy link
Copy Markdown
Member

Pushed e75bc411d. Extends the fix to the other runtime CLIs and reworks the docs for readability.

Runtime coverage. A reviewer pass corrected my first model of the gateway rule: OPA matches the binaries: glob against the connecting process's /proc/<pid>/exe or any ancestor's, so node-wrapped runtimes (pi, and codex via bin/codex.jsvendor/<triple>/bin/codex) are already admitted by **/node, and the pin-bump hazard is specific to runtimes exec'd directly through a symlink — which is exactly why Claude Code's claude.exe was denied. TestScaffoldProfilesAllowRuntimeBinaries now walks config.ValidRuntimes() and fails for any selectable runtime with no declared mapping. OpenCode is exec'd directly too, but it is a stub outside the image and its installed path depends on a Containerfile decision not yet made, so nothing is guessed at in the profiles: the note lives on OpenCodeRuntime and the test fails the day opencode becomes selectable.

Docs. Rationale moved out of the how-to pages: the profile snippet carries one comment, the error text is a Troubleshooting row in both user guides with the grep DENIED recipe, customizing-agents.md shows the warning users actually see, and the mechanism lives in a contributor section with a checklist step and a CODEX_VERSION re-check row.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:59 PM UTC · Completed 6:38 PM UTC

Commit: e75bc41 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $4.66

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review September 3, 2026 18:38

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Sep 3, 2026
@waynesun09
waynesun09 added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 094191b Sep 3, 2026
54 checks passed
@waynesun09
waynesun09 deleted the agent/6971-scaffold-vertex-binary-allowlist branch September 3, 2026 18:53
@fullsend-ai-retro

fullsend-ai-retro Bot commented Sep 3, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 6:55 PM UTC · Completed 7:13 PM UTC

Commit: e75bc41 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $4.46

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #6975 — scaffold Vertex profile binary allowlist fix

Timeline

  1. 13:58Z#6962 filed: functional tests broken on main with policy_denied errors since 0.40.0 image repin.
  2. 14:04Z — Triage on functional-tests: every triage case fails with API Error policy_denied on main since 2026-09-03 #6962 correctly identified the 0.40.0 repin as the suspect but misattributed the mechanism (model authorization vs OPA binary allowlist). Despite recommending investigation ("confirm by running with pre-mint: add retro and prioritize roles to test defaults #1160 images, check Vertex gateway logs"), it labeled the issue ready-to-code. Cost: $0.84.
  3. 14:06–14:31Z — Code agent auto-dispatched on functional-tests: every triage case fails with API Error policy_denied on main since 2026-09-03 #6962. Built a model-pinning fix (wrong diagnosis), also modified .github/workflows/functional-tests.yml (no workflows permission), push rejected. Cost: $10.15 wasted.
  4. 16:26Z — Human (waynesun09) corrected the root cause: OPA binary allowlist denied claude.exe, not model auth. Filed #6971 with precise two-part fix proposal.
  5. 16:28Z — Triage on sandbox: scaffold Vertex profile lacks **/claude.exe, so every 0.40.0-image Claude run fails with policy_denied #6971 confirmed the analysis. Cost: $0.64.
  6. 16:33Z/fs-code dispatched with detailed instructions (two commits, no workflow changes).
  7. 16:36–16:50Z — Code agent succeeded, produced PR #6975. Cost: $4.15.
  8. 17:12Z — Review bot posted 3 low-severity findings (test-adequacy, naming-consistency, stale-doc).
  9. 17:14–17:22Z — Fix agent dispatched, but waynesun09 force-pushed the branch at 17:17Z while the fix agent was running. Push rejected. Cost: $1.19 wasted.
  10. 17:30Z — waynesun09 addressed review findings inline, added fullsend-no-fix label.
  11. 18:18Z — ralphbean approved. 18:38Z — Review bot approved. 18:53Z — Merged.

Total agent spend: ~$17. Wasted: ~$11.3 (67%).

What went well

Evidence for existing issues

Proposals filed

waynesun09 added a commit to fullsend-ai/agents that referenced this pull request Sep 4, 2026
Rebased onto origin/main, which brings fullsend-ai/fullsend#6975 into
the generator's embedded scaffold: the Vertex profile now allows
`**/claude.exe` and `**/pi` alongside `**/claude`. On the v0.40.0 sandbox
image the Claude binary is `claude.exe`, so before that fix it matched no
profile's binaries list, no egress policy applied to it, and every
request it made was refused — which is what a local `--runtime claude`
run of this example hit.

The example's copied profile carries the fix now, and examples/README.md
gains the step that was missing for anyone trying to run one: these are
deliberately absent from this repository's config.yaml, so `fullsend
run` cannot resolve them until you register a copy. The exact error and
the `fullsend agent add` command that avoids it are both in the README —
found by hitting it.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
waynesun09 added a commit to fullsend-ai/agents that referenced this pull request Sep 11, 2026
Rebased onto origin/main, which brings fullsend-ai/fullsend#6975 into
the generator's embedded scaffold: the Vertex profile now allows
`**/claude.exe` and `**/pi` alongside `**/claude`. On the v0.40.0 sandbox
image the Claude binary is `claude.exe`, so before that fix it matched no
profile's binaries list, no egress policy applied to it, and every
request it made was refused — which is what a local `--runtime claude`
run of this example hit.

The example's copied profile carries the fix now, and examples/README.md
gains the step that was missing for anyone trying to run one: these are
deliberately absent from this repository's config.yaml, so `fullsend
run` cannot resolve them until you register a copy. The exact error and
the `fullsend agent add` command that avoids it are both in the README —
found by hitting it.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fullsend-no-fix Skip bot-triggered fix agent runs ready-for-merge All reviewers approved — ready to merge ready-for-review Triggers review agent dispatch risk/moderate PR risk: moderate

Projects

None yet

2 participants