Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pkg/workflow/compiler_yaml_ai_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,17 @@ func (c *Compiler) generateAgentRunSteps(yaml *strings.Builder, data *WorkflowDa
// connects to via host.docker.internal:18443.
c.generateStartCliProxyStep(yaml, data)

// Refresh sbx credentials immediately before AWF execution. Docker Hub OAuth
// tokens obtained during the daemon-setup step can expire between workflow steps,
// causing "user is not authenticated to Docker" errors when AWF calls `sbx create`.
if isDockerSbxRuntime(data) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice — gating the credential refresh on isDockerSbxRuntime keeps this cleanly scoped to the docker-sbx runtime. 👍

refreshStep := generateDockerSbxCredentialRefreshStep()
for _, line := range refreshStep {
yaml.WriteString(line)
yaml.WriteString("\n")
}
}

// Add AI execution step using the agentic engine
compilerYamlLog.Printf("Generating engine execution steps for %s", engine.GetID())
c.generateEngineExecutionSteps(yaml, data, engine, logFileFull)
Expand Down
19 changes: 19 additions & 0 deletions pkg/workflow/docker_sbx_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,25 @@ func generateDockerSbxAuthAndDaemonStep() GitHubActionStep {
})
}

// generateDockerSbxCredentialRefreshStep creates a step that re-authenticates the
// sbx daemon with Docker Hub immediately before AWF runs the agent. OAuth tokens
// obtained by `sbx login` during the daemon-setup step can expire or be invalidated
// by the policy-reset cycle, so a fresh login right before execution prevents
// "user is not authenticated to Docker" errors when AWF calls `sbx create`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clear doc comment explaining why the token can expire — helpful context for future maintainers.

func generateDockerSbxCredentialRefreshStep() GitHubActionStep {
return GitHubActionStep([]string{
" - name: Refresh sbx credentials",
" env:",
" DOCKER_PAT_VAL: ${{ secrets.DOCKER_PAT }}",
" DOCKER_USERNAME_VAL: ${{ secrets.DOCKER_USERNAME }}",
" run: |",
` # Re-authenticate sbx immediately before AWF runs.`,
` # Docker Hub OAuth tokens from sbx login can expire between steps.`,
` printf '%s' "$DOCKER_PAT_VAL" | sbx login --username "$DOCKER_USERNAME_VAL" --password-stdin`,
` echo "✅ sbx credentials refreshed"`,
})
}

// generateDockerSbxPreFlightStep creates a step that verifies the sbx stack works
// end-to-end before the MCP gateway and AWF container setup begins.
func generateDockerSbxPreFlightStep() GitHubActionStep {
Expand Down
29 changes: 29 additions & 0 deletions pkg/workflow/docker_sbx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,25 @@ func TestGenerateDockerSbxInstallSteps(t *testing.T) {
assert.Contains(t, content, "sbx rm", "must remove the test sandbox")
assert.Contains(t, content, "✅ sbx ready", "must confirm readiness")
})

t.Run("credential refresh step", func(t *testing.T) {
step := generateDockerSbxCredentialRefreshStep()
require.NotEmpty(t, step, "credential refresh step must not be empty")
content := strings.Join(step, "\n")
assert.Contains(t, content, "Refresh sbx credentials", "must have correct step name")
assert.Contains(t, content, "sbx login", "must re-authenticate with sbx login")
assert.Contains(t, content, "DOCKER_PAT_VAL: ${{ secrets.DOCKER_PAT }}", "must use env for DOCKER_PAT")
assert.Contains(t, content, "DOCKER_USERNAME_VAL: ${{ secrets.DOCKER_USERNAME }}", "must use env for DOCKER_USERNAME")
assert.Contains(t, content, "✅ sbx credentials refreshed", "must confirm refresh")
// The run: section must use env var references, not raw secret expressions.
parts := strings.SplitN(content, "run: |", 2)
require.Len(t, parts, 2, "step must have a run: section")
runBody := parts[1]
assert.NotContains(t, runBody, "${{ secrets.DOCKER_PAT }}",
"raw secrets expression must not appear in shell commands")
assert.NotContains(t, runBody, "${{ secrets.DOCKER_USERNAME }}",
"raw secrets expression must not appear in shell commands")
})
}

// TestDockerSbxInstallStepOrderInBuildNpmEngineInstallStepsWithAWF verifies that all
Expand Down Expand Up @@ -527,4 +546,14 @@ sandbox:

// --container-runtime sbx must appear in the AWF invocation.
assert.Contains(t, lockStr, "--container-runtime sbx", "AWF invocation must include --container-runtime sbx")

// Credential refresh step must be present.
assert.Contains(t, lockStr, "Refresh sbx credentials", "compiled workflow must include credential refresh step before execution")

// Credential refresh step must appear AFTER pre-flight but BEFORE execution.
refreshPos := strings.Index(lockStr, "Refresh sbx credentials")
preflightPos := strings.Index(lockStr, "pre-flight smoke test")
execPos := strings.Index(lockStr, "agentic_execution")
assert.Greater(t, refreshPos, preflightPos, "credential refresh must come after pre-flight step")
assert.Less(t, refreshPos, execPos, "credential refresh must come before agent execution step")
}
Loading