Skip to content

feat(cli): mint agent tokens in the binary instead of workflows#2389

Open
ggallen wants to merge 1 commit into
mainfrom
worktree-mint-in-binary
Open

feat(cli): mint agent tokens in the binary instead of workflows#2389
ggallen wants to merge 1 commit into
mainfrom
worktree-mint-in-binary

Conversation

@ggallen

@ggallen ggallen commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds mintAgentToken() to fullsend run that calls the mint service using the harness's role field, setting GH_TOKEN and role-specific env vars (PUSH_TOKEN, REVIEW_TOKEN, RETRO_SANDBOX_TOKEN) before env expansion
  • Removes token passthrough env vars from all 6 reusable workflow files — workflows retain mint-token action only for actions/checkout
  • Adds MINT_REPOS env var support for retro's multi-repo case
  • Makes agents self-contained: the harness role field now drives token acquisition at runtime, aligning with ADR-0045's goal of portable harness definitions

Test plan

  • All 19 new unit tests pass (TestMintAgentToken_*, TestResolveMintRepos_*, TestRoleTokenVars_*)
  • 100% coverage on mintAgentToken and resolveMintRepos
  • Full make go-test passes (including -race)
  • make go-vet and make lint pass
  • Deploy to test org, trigger each agent type (code, triage, review, fix, retro, prioritize), verify tokens work end-to-end

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

Site preview

Preview: https://80f2eeac-site.fullsend-ai.workers.dev

Commit: 54ff8d598499174942ee51a9788c5e2b5c40f114

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:26 PM UTC · Completed 2:44 PM UTC
Commit: b14cf6f · View workflow run →

@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.77465% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/cli/run.go 95.77% 2 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml — This PR modifies 6 files under .github/workflows/, which is a protected path requiring human approval. The PR has no linked issue providing justification for modifying governance/infrastructure files. Human approval is required regardless of context.
    Remediation: Link an issue that documents authorization for this change, or obtain explicit human approval on the PR.

Low

  • [error-handling-gaps] internal/cli/run.go:315 — When mintURL is empty and h.Role is non-empty, mintAgentToken returns (false, nil) and the caller prints a StepWarn. The agent proceeds and will fail at ValidateRunnerEnvWith because GH_TOKEN and role-specific vars will not be set. The system fails safely (validation catches it), but the warning text "skipping token minting" could mislead operators into thinking minting is optional when in CI it is required.

  • [GHA-workflow-command-injection] internal/cli/run.goExpiresAt field from the mint service response is sanitized via strings.Map allowlist (digits, -:TZ+), which effectively prevents :: injection. Token is validated against mintTokenPattern before ::add-mask::, preventing workflow command injection through the token value. Residual risk is negligible.

  • [stale-workflow-pattern] docs/superpowers/plans/2026-05-04-retro-agent.md:847 — The retro agent implementation plan shows a workflow snippet with RETRO_GH_TOKEN as an env var. This reflects the old workflow-passthrough token model which this PR replaces with binary-based minting. As a planning document, the operational risk of this staleness is low.

Info

  • [permission-reduction] .github/workflows/reusable-*.yml — Removal of CODE_GH_TOKEN, FIX_GH_TOKEN, PUSH_TOKEN, PUSH_TOKEN_SOURCE, REVIEW_TOKEN, RETRO_SANDBOX_TOKEN, and GH_TOKEN env var passthrough from 6 workflow files reduces the surface of token exposure. Positive security improvement.

  • [secrets-handling] internal/cli/run.go — The new mintAgentToken correctly masks the minted token via ::add-mask:: before setting env vars. Token is validated against mintTokenPattern before masking, preventing workflow command injection through the token value.

  • [migration-path-unclear] — The PR body references ADR-0045 but doesn't clarify which phase this PR represents, and whether the dual-state (workflows AND binary both minting tokens) is intentional transitional architecture.

Previous run

Review

Findings

High

  • [protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml — This PR modifies 6 files under .github/workflows/, which is a protected path requiring human approval. The PR has no linked issue providing justification for modifying governance/infrastructure files. Human approval is required regardless of context.
    Remediation: Link an issue that documents authorization for this change, or obtain explicit human approval on the PR.

Medium

  • [test-integrity] internal/cli/run_test.gomintAgentToken calls os.Setenv for GH_TOKEN, PUSH_TOKEN, PUSH_TOKEN_SOURCE, REVIEW_TOKEN, and RETRO_SANDBOX_TOKEN inside the function under test. These mutations are NOT managed by t.Setenv and persist across tests, creating ordering dependencies between test functions.

  • [scope-coherence] internal/cli/run.gosetupStatusNotifier (existing code) uses resolveRole(agentName) to derive the role from the agent name, while the new mintAgentToken receives h.Role from the harness. This creates an inconsistency: two call sites in runAgent derive the canonical role differently.

  • [stale-token-reference] docs/architecture.md:602 — The architecture document's 'MVP embodiment' section references PUSH_TOKEN in the context of post-script credential handling. While the env var name is unchanged, the token source has changed from workflow passthrough to binary-based minting.

Low

  • [error-handling-gaps] internal/cli/run.go:310 — When mintURL is empty and h.Role is non-empty, mintAgentToken returns (false, nil) and the caller prints a StepWarn. The agent still proceeds and will fail later at ValidateRunnerEnvWith. The added warning partially addresses the prior finding, and downstream env validation provides a hard stop with a clear error.

  • [GHA-workflow-command-injection] internal/cli/run.goExpiresAt field from the mint service response is sanitized via strings.Map allowlist (digits, -:TZ+), which effectively prevents :: injection. Lipgloss ANSI wrapping provides a secondary defense. Residual risk is negligible.

  • [stale-token-reference] docs/ADRs/0032-safe-push-wrapper-for-sandboxed-agents.md:159 — ADR 0032 references PUSH_TOKEN in the context of Tier 1 post-script push. As an ADR (historical record), the reference is still conceptually correct — the post-script still uses a push token, now minted by the binary rather than passed from the workflow.

Info

  • [permission-reduction] .github/workflows/reusable-*.yml — Removal of CODE_GH_TOKEN, PUSH_TOKEN, PUSH_TOKEN_SOURCE, REVIEW_TOKEN, RETRO_SANDBOX_TOKEN, and GH_TOKEN env var passthrough from 6 workflow files reduces the surface of token exposure. Positive security improvement.

  • [secrets-handling] internal/cli/run.go — The new mintAgentToken correctly masks the minted token via ::add-mask:: before setting env vars. Token is validated against mintTokenPattern before masking, preventing workflow command injection through the token value.

Previous run (2)

Review

Findings

High

  • [protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml — This PR modifies 6 files under .github/workflows/, which is a protected path requiring human approval. The PR has no linked issue providing justification for modifying governance/infrastructure files. Human approval is required regardless of context.
    Remediation: Link an issue that documents authorization for this change, or obtain explicit human approval on the PR.

Low

  • [error-handling-gaps] internal/cli/run.go:310mintAgentToken returns (false, nil) when mintURL is empty, and the boolean is discarded by the caller in runAgent. If mint-url is accidentally omitted in CI, minting silently does nothing and the agent proceeds without tokens, failing later at env validation or with a 401. A warning log when minting is skipped despite a non-empty harness role would aid debugging.

  • [GHA-workflow-command-injection] internal/cli/run.goExpiresAt field from the mint service response is interpolated into printer.StepDone output without sanitization. Lipgloss ANSI wrapping currently prevents GHA from parsing :: commands, but if NO_COLOR=1 is set and the mint service is compromised, ExpiresAt could theoretically inject workflow commands. The token itself is properly validated against mintTokenPattern before use in ::add-mask::.

Info

  • [naming-convention] internal/cli/run.goroleTokenVars could be more explicit as roleTokenEnvVars to match the naming pattern of similar environment variable maps in the codebase (e.g., trafficEnvVars, verifyEnvVars).

  • [test-adequacy] internal/cli/run_test.gomintAgentToken tests set env vars via os.Setenv inside the function under test (GH_TOKEN, PUSH_TOKEN, etc.) but do not restore them. t.Setenv only manages variables explicitly registered with it, so these side effects leak between tests, creating ordering dependencies.

  • [permission-reduction] .github/workflows/reusable-*.yml — Removal of CODE_GH_TOKEN, PUSH_TOKEN, PUSH_TOKEN_SOURCE, REVIEW_TOKEN, RETRO_SANDBOX_TOKEN, and GH_TOKEN env var passthrough from 6 workflow files reduces the surface of token exposure in workflow logs and step environments. Positive security improvement.

Previous run (3)

Review

Findings

High

  • [protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml — This PR modifies 6 files under .github/workflows/, which is a protected path requiring human approval. The PR has no linked issue providing justification for modifying governance/infrastructure files. Human approval is required regardless of context.

Medium

  • [input-validation] internal/cli/run.goresolveMintRepos() accepts repo names from MINT_REPOS and REPO_FULL_NAME environment variables without validating them against mintcore.RepoNamePattern. The existing mint token CLI command at minttoken.go:52 validates each repo name with this pattern. Unvalidated repo names are sent to the mint service.
    Remediation: Add mintcore.RepoNamePattern.MatchString() validation for each resolved repo name in resolveMintRepos(), matching the validation in minttoken.go.

  • [stale-reference] docs/guides/user/running-agents-locally.md:149 — Documentation references REVIEW_TOKEN environment variable. While this remains valid for local runs (no mint service), the guide should note that in CI, tokens are now automatically minted by the binary when --mint-url is provided.
    Remediation: Add a note to the review agent section about automatic token minting in CI.

  • [stale-reference] docs/guides/user/running-agents-locally.md:170 — Documentation references PUSH_TOKEN and PUSH_TOKEN_SOURCE for the code agent. Same situation: still valid for local runs but incomplete without noting CI auto-minting.
    Remediation: Update the code agent section to mention the new token minting mechanism in CI.

Low

  • [dead-code] internal/cli/run.goroleTokenVars["retro"] sets RETRO_SANDBOX_TOKEN, but no harness YAML or script references this variable. The retro harness (retro.yaml) uses GH_TOKEN, which is already set unconditionally by mintAgentToken. Setting RETRO_SANDBOX_TOKEN has no effect.

  • [input-validation] internal/cli/run.gomintAgentToken does not call mintcore.ValidateRoleName(canonicalRole) before passing the role to the mint service, unlike the mint token CLI command at minttoken.go:58. Server-side validation exists but client-side validation provides better error messages and consistency.

  • [pattern-inconsistency] internal/cli/run.goPUSH_TOKEN_SOURCE special-case handling via string equality check (if v == "PUSH_TOKEN_SOURCE") is a minor code smell. A constant or struct type would make the behavior self-documenting.

  • [doc-style] internal/cli/run.goroleTokenVars and mintAgentToken lack godoc comments. Most helpers in this file have godoc comments.

  • [stale-reference] docs/superpowers/plans/2026-05-04-retro-agent.md:194 — Plan doc references GH_TOKEN in env template. While GH_TOKEN is still the correct variable name, the token source has changed.

  • [stale-reference] docs/guides/user/customizing-agents.md:44 — Guide shows PUSH_TOKEN in runner_env example. The harness config still uses this variable, so the example is technically correct, but could note that the value is now auto-populated via mint in CI.

  • [stale-reference] docs/plans/adr-0045-forge-portable-harness-phase2.md:129 — Phase 2 plan references PUSH_TOKEN and PUSH_TOKEN_SOURCE in forge.github.runner_env migration context.

  • [stale-reference] docs/plans/adr-0045-forge-portable-harness-phase2.md:135 — Phase 2 plan references REVIEW_TOKEN in forge.github.runner_env migration context.

Info

  • [permission-reduction] .github/workflows/reusable-*.yml — Removal of *_GH_TOKEN, PUSH_TOKEN, PUSH_TOKEN_SOURCE, REVIEW_TOKEN, RETRO_SANDBOX_TOKEN, and GH_TOKEN env var passthrough from 6 workflow files reduces the surface of token exposure in workflow logs and step environments. Positive security improvement.

  • [naming-convention] internal/cli/run.goroleTokenVars could be more explicit as roleTokenEnvVars to clarify these are environment variable names.

  • [potential-update-needed] docs/ADRs/0032-safe-push-wrapper-for-sandboxed-agents.md:157 — ADR references PUSH_TOKEN used by post-script. Still functionally accurate but token source has changed.

  • [potential-update-needed] docs/architecture.md:602 — Architecture doc mentions PUSH_TOKEN in post-code.sh context. Functionally accurate but source changed.

Previous run (4)

Review

Findings

High

  • [protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml — This PR modifies 6 files under .github/workflows/, which is a protected path requiring human approval. The PR has no linked issue providing justification for modifying governance/infrastructure files. Human approval is required regardless of context.

Medium

  • [input-validation] internal/cli/run.goresolveMintRepos() accepts repo names from MINT_REPOS and REPO_FULL_NAME environment variables without validating them against mintcore.RepoNamePattern. The existing mint token CLI command at minttoken.go:52 validates each repo name with this pattern. Unvalidated repo names are sent to the mint service.
    Remediation: Add mintcore.RepoNamePattern.MatchString() validation for each resolved repo name in resolveMintRepos(), matching the validation in minttoken.go.

  • [stale-reference] docs/guides/user/running-agents-locally.md:149 — Documentation references REVIEW_TOKEN environment variable. While this remains valid for local runs (no mint service), the guide should note that in CI, tokens are now automatically minted by the binary when --mint-url is provided.
    Remediation: Add a note to the review agent section about automatic token minting in CI.

  • [stale-reference] docs/guides/user/running-agents-locally.md:170 — Documentation references PUSH_TOKEN and PUSH_TOKEN_SOURCE for the code agent. Same situation: still valid for local runs but incomplete without noting CI auto-minting.
    Remediation: Update the code agent section to mention the new token minting mechanism in CI.

Low

  • [dead-code] internal/cli/run.goroleTokenVars["retro"] sets RETRO_SANDBOX_TOKEN, but no harness YAML or script references this variable. The retro harness (retro.yaml) uses GH_TOKEN, which is already set unconditionally by mintAgentToken. Setting RETRO_SANDBOX_TOKEN has no effect.

  • [input-validation] internal/cli/run.gomintAgentToken does not call mintcore.ValidateRoleName(canonicalRole) before passing the role to the mint service, unlike the mint token CLI command at minttoken.go:58. Server-side validation exists but client-side validation provides better error messages and consistency.

  • [pattern-inconsistency] internal/cli/run.goPUSH_TOKEN_SOURCE special-case handling via string equality check (if v == "PUSH_TOKEN_SOURCE") is a minor code smell. A constant or struct type would make the behavior self-documenting.

  • [doc-style] internal/cli/run.goroleTokenVars and mintAgentToken lack godoc comments. Most helpers in this file have godoc comments.

  • [stale-reference] docs/superpowers/plans/2026-05-04-retro-agent.md:194 — Plan doc references GH_TOKEN in env template. While GH_TOKEN is still the correct variable name, the token source has changed.

  • [stale-reference] docs/guides/user/customizing-agents.md:44 — Guide shows PUSH_TOKEN in runner_env example. The harness config still uses this variable, so the example is technically correct, but could note that the value is now auto-populated via mint in CI.

  • [stale-reference] docs/plans/adr-0045-forge-portable-harness-phase2.md:129 — Phase 2 plan references PUSH_TOKEN and PUSH_TOKEN_SOURCE in forge.github.runner_env migration context.

  • [stale-reference] docs/plans/adr-0045-forge-portable-harness-phase2.md:135 — Phase 2 plan references REVIEW_TOKEN in forge.github.runner_env migration context.

Info

  • [permission-reduction] .github/workflows/reusable-*.yml — Removal of *_GH_TOKEN, PUSH_TOKEN, PUSH_TOKEN_SOURCE, REVIEW_TOKEN, RETRO_SANDBOX_TOKEN, and GH_TOKEN env var passthrough from 6 workflow files reduces the surface of token exposure in workflow logs and step environments. Positive security improvement.

  • [naming-convention] internal/cli/run.goroleTokenVars could be more explicit as roleTokenEnvVars to clarify these are environment variable names.

  • [potential-update-needed] docs/ADRs/0032-safe-push-wrapper-for-sandboxed-agents.md:157 — ADR references PUSH_TOKEN used by post-script. Still functionally accurate but token source has changed.

  • [potential-update-needed] docs/architecture.md:602 — Architecture doc mentions PUSH_TOKEN in post-code.sh context. Functionally accurate but source changed.

Previous run (5)

Review

Findings

Critical

  • [missing-environment-variable] .github/workflows/reusable-triage.yml:144 — The triage workflow passes mint-url to the composite action (line 154), which causes --mint-url to reach the binary. The binary's new mintAgentToken() calls resolveMintRepos(), which requires either MINT_REPOS or REPO_FULL_NAME in the process environment. The "Run triage agent" step env block (line 147) contains only GITHUB_ISSUE_URL — neither REPO_FULL_NAME nor MINT_REPOS is set. resolveMintRepos() will return an error, causing mintAgentToken to fail and the agent to crash.
    Remediation: Add REPO_FULL_NAME: ${{ inputs.source_repo }} to the "Run triage agent" env block.

  • [missing-environment-variable] .github/workflows/reusable-prioritize.yml:143 — The diff removes PRIORITIZE_GH_TOKEN from the setup-agent-env step (line 133), but the "Run prioritize agent" step (lines 143–150) does not pass mint-url in its with block. Without --mint-url and without FULLSEND_MINT_URL in the process environment, mintAgentToken() returns early as a no-op and GH_TOKEN is never set. The prioritize harness references ${GH_TOKEN} in runner_env, which will expand to empty. The agent will fail on any GitHub API call.
    Remediation: Add mint-url: ${{ inputs.mint_url }} to the "Run prioritize agent" with block, and add REPO_FULL_NAME: ${{ inputs.source_repo }} (or MINT_REPOS) to its env block.

High

  • [protected-path] .github/workflows/ — This PR modifies 6 files under .github/workflows/, which is a protected path requiring human approval. The PR has no linked issue providing justification for modifying governance/infrastructure files. Affected files: reusable-code.yml, reusable-fix.yml, reusable-prioritize.yml, reusable-retro.yml, reusable-review.yml, reusable-triage.yml.

Medium

  • [architectural-coherence] .github/workflows/reusable-*.yml — ADR-0029 ("Central token mint and shared apps for a secretless .fullsend") states that workflows obtain forge tokens from the mint when they need GitHub API access. This PR moves token minting into the binary, creating a different trust boundary. The PR references ADR-0045 but does not clarify how this relates to ADR-0029's workflow-level minting pattern.
    Remediation: Clarify whether this implements a variant of ADR-0029 or supersedes its workflow-level minting pattern, and update documentation accordingly.

Low

  • [missing-authorization] — This PR has no linked issue. Non-trivial changes to security boundary and credential flow benefit from explicit authorization.

  • [scope-alignment] .github/workflows/reusable-retro.yml — The PR adds a MINT_REPOS env var to reusable-retro.yml. While the PR body mentions it, the conditional logic mirrors the existing "Mint retro token" step at line 117. Consider updating the PR description to describe the behavioral mapping explicitly.

  • [token-validation] internal/cli/run.gomintAgentToken uses mintTokenPattern only for masking decisions, not for validation. Unlike minttoken.go which rejects tokens failing the pattern match, a malformed token from the mint service would be silently accepted. Defense-in-depth improvement.

  • [dead-code] internal/cli/run.go:1990roleTokenVars["retro"] sets RETRO_SANDBOX_TOKEN, but no harness YAML, script, or Go code reads this env var. The retro harness only uses GH_TOKEN in runner_env. The only prior consumer (the workflow env block) is removed by this PR.

  • [error-handling] internal/cli/run.go:2005 — Error wrapping format "resolving mint repos: %w" is less specific than the codebase pattern of including identifiers (e.g., "loading env file %s: %w").

Info

  • [permission-reduction] .github/workflows/reusable-*.yml — Removal of *_GH_TOKEN, PUSH_TOKEN, PUSH_TOKEN_SOURCE, and similar env vars from 6 workflow files reduces the surface of token exposure in workflow logs and step environments.

  • [phase-alignment] — The PR's use of h.Role to drive mint calls aligns with ADR-0045 Phase 2/3 intent but doesn't reference which phase it belongs to.

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

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

fullsend-ai-review Bot commented Jun 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:25 PM UTC · Completed 3:39 PM UTC
Commit: a7f2e35 · View workflow run →

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:51 PM UTC · Completed 4:06 PM UTC
Commit: 9e99539 · View workflow run →

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

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

fullsend-ai-review Bot commented Jun 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:17 PM UTC · Completed 4:35 PM UTC
Commit: 0d51413 · View workflow run →

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

Comment thread internal/cli/run.go
Move GitHub App token minting from workflow YAML into the fullsend
binary. The harness role field now drives token acquisition via
mintclient when --mint-url is provided. Tokens are set in the process
environment so RunnerEnv expansion and host_files pick them up
automatically.

Signed-off-by: Greg Allen <gallen@redhat.com>
Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:46 PM UTC · Completed 5:01 PM UTC
Commit: 54ff8d5 · View workflow run →

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

Comment thread internal/cli/run.go
@rh-hemartin

Copy link
Copy Markdown
Member

Shouldn't you remove the mint-token step from the workflows as well? And also remove the action completely?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants