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
2 changes: 1 addition & 1 deletion docs/src/content/docs/reference/safe-outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ Optionally include `item_url` (GitHub issue URL) to add the issue as the first p

Manages GitHub Projects boards. Requires a write-capable PAT or GitHub App token ([project token authentication](/gh-aw/patterns/project-ops/#project-token-authentication)); default `GITHUB_TOKEN` lacks Projects v2 access. Update-only by default; set `create_if_missing: true` to create boards (requires appropriate token permissions).

When using `github-app`, issue-backed project item resolution also requires `issues: read` on the minted token (in addition to `organization-projects: write`).
When using `github-app`, issue-backed project item resolution also requires `issues: read` on the minted token (in addition to `organization-projects: write`). This applies to `update-project`, and also to `create-project` when `item_url` is used to resolve an issue into a project item.
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

This note about create-project + item_url is placed under the update-project section, but the create-project section above is where users will look for create-project token requirements (especially since item_url is documented there). Consider duplicating/moving this guidance closer to the create-project documentation so the permission requirement isn’t missed.

Copilot uses AI. Check for mistakes.

```yaml wrap
safe-outputs:
Expand Down
41 changes: 41 additions & 0 deletions pkg/workflow/safe_outputs_app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,44 @@ Test workflow with update-project permissions.
assert.Contains(t, stepsStr, "permission-issues: read", "GitHub App token should include issues read permission for issue-backed project items")
assert.Contains(t, stepsStr, "permission-contents: read", "GitHub App token should include contents read permission")
}

// TestSafeOutputsAppTokenCreateProjectWithItemURLIssuesReadPermission tests that issues read permission
// is included in the GitHub App token minting step when create-project is configured with item_url.
func TestSafeOutputsAppTokenCreateProjectWithItemURLIssuesReadPermission(t *testing.T) {
compiler := NewCompiler(WithVersion("1.0.0"))

markdown := `---
on: issues
safe-outputs:
create-project:
target-owner: "my-org"
github-app:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
---

# Test Workflow

Test workflow with create-project item_url permissions.
`
Comment on lines +206 to +224
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

This test name/comment says create-project is configured "with item_url", but item_url is not part of the safe-outputs.create-project frontmatter config and the markdown fixture here does not (and cannot) express whether the agent will send an item_url at runtime. As written, the test is asserting that enabling create-project causes the app token minting step to include permission-issues: read unconditionally, so the test should be renamed/reworded to match what it actually validates.

Copilot uses AI. Check for mistakes.

tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.md")
err := os.WriteFile(testFile, []byte(markdown), 0644)
require.NoError(t, err, "Failed to write test file")

workflowData, err := compiler.ParseWorkflowFile(testFile)
require.NoError(t, err, "Failed to parse markdown content")
require.NotNil(t, workflowData.SafeOutputs, "SafeOutputs should not be nil")
require.NotNil(t, workflowData.SafeOutputs.CreateProjects, "CreateProjects should not be nil")

job, _, err := compiler.buildConsolidatedSafeOutputsJob(workflowData, "main", testFile)
require.NoError(t, err, "Failed to build safe_outputs job")
require.NotNil(t, job, "Job should not be nil")

stepsStr := strings.Join(job.Steps, "")

assert.Contains(t, stepsStr, "permission-organization-projects: write", "GitHub App token should include organization projects write permission")
assert.Contains(t, stepsStr, "permission-issues: read", "GitHub App token should include issues read permission for issue-backed project items")
assert.Contains(t, stepsStr, "permission-contents: read", "GitHub App token should include contents read permission")
}
1 change: 1 addition & 0 deletions pkg/workflow/safe_outputs_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func ComputePermissionsForSafeOutputs(safeOutputs *SafeOutputsConfig) *Permissio
if safeOutputs.CreateProjects != nil && !isHandlerStaged(safeOutputs.Staged, safeOutputs.CreateProjects.Staged) {
safeOutputsPermissionsLog.Print("Adding permissions for create-project")
permissions.Merge(NewPermissionsContentsReadProjectsWrite())
permissions.Set(PermissionIssues, PermissionRead)
Comment on lines 202 to +205
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

ComputePermissionsForSafeOutputs now unconditionally adds issues: read when create-project is enabled. In actions/setup/js/create_project.cjs, issues access is only needed when item_url is provided (to resolve the issue node ID and add it to the project), so this expands the minted GitHub App token permissions even for create-project usages that never set item_url. Consider making this conditional (e.g., via an explicit config flag that enables item_url support) or updating the documentation to clarify that enabling create-project will always mint a token with issues: read due to the optional item_url feature.

Copilot uses AI. Check for mistakes.
}
if safeOutputs.UpdateProjects != nil && !isHandlerStaged(safeOutputs.Staged, safeOutputs.UpdateProjects.Staged) {
safeOutputsPermissionsLog.Print("Adding permissions for update-project")
Expand Down
3 changes: 2 additions & 1 deletion pkg/workflow/safe_outputs_permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) {
},
},
{
name: "create-project requires organization-projects write",
name: "create-project requires organization-projects write and issues read",
safeOutputs: &SafeOutputsConfig{
CreateProjects: &CreateProjectsConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")},
Expand All @@ -393,6 +393,7 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) {
expected: map[PermissionScope]PermissionLevel{
PermissionContents: PermissionRead,
PermissionOrganizationProj: PermissionWrite,
PermissionIssues: PermissionRead,
},
},
{
Expand Down
Loading