diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md
index b876d600a05..81636759791 100644
--- a/docs/src/content/docs/reference/safe-outputs.md
+++ b/docs/src/content/docs/reference/safe-outputs.md
@@ -462,6 +462,7 @@ Manages GitHub Projects boards. Requires PAT or GitHub App token ([`GH_AW_PROJEC
 safe-outputs:
   update-project:
     max: 20                         # max operations (default: 10)
+    project: "https://github.com/orgs/myorg/projects/42"  # default project URL (optional)
     github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}
     views:                          # optional: auto-create views
       - name: "Sprint Board"
@@ -473,7 +474,11 @@ safe-outputs:
         layout: roadmap
 ```
 
-Agent must provide full project URL (e.g., `https://github.com/orgs/myorg/projects/42`). Optional `campaign_id` applies `z_campaign_<id>` labels for [Campaign Workflows](/gh-aw/guides/campaigns/). Exposes outputs: `project-id`, `project-number`, `project-url`, `campaign-id`, `item-id`.
+**Configuration options:**
+- `project` (optional): Default project URL for operations. When specified, agent messages can omit the `project` field and will use this URL by default. Overridden by explicit `project` field in agent output.
+- Agent can provide full project URL (e.g., `https://github.com/orgs/myorg/projects/42`) in each message, or rely on the configured default.
+- Optional `campaign_id` applies `z_campaign_<id>` labels for [Campaign Workflows](/gh-aw/guides/campaigns/).
+- Exposes outputs: `project-id`, `project-number`, `project-url`, `campaign-id`, `item-id`.
 
 #### Supported Field Types
 
@@ -591,16 +596,20 @@ Creates status updates on GitHub Projects boards to communicate campaign progres
 safe-outputs:
   create-project-status-update:
     max: 1                          # max updates per run (default: 1)
+    project: "https://github.com/orgs/myorg/projects/73"  # default project URL (optional)
     github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}
 ```
 
-Agent provides full project URL, status update body (markdown), status indicator, and date fields. Typically used by [Campaign Workflows](/gh-aw/guides/campaigns/) to automatically post run summaries.
+**Configuration options:**
+- `project` (optional): Default project URL for status updates. When specified, agent messages can omit the `project` field and will use this URL by default. Overridden by explicit `project` field in agent output.
+- Agent can provide full project URL in each message, or rely on the configured default.
+- Typically used by [Campaign Workflows](/gh-aw/guides/campaigns/) to automatically post run summaries.
 
 #### Required Fields
 
 | Field | Type | Description |
 |-------|------|-------------|
-| `project` | URL | Full GitHub project URL (e.g., `https://github.com/orgs/myorg/projects/73`) |
+| `project` | URL | Full GitHub project URL (e.g., `https://github.com/orgs/myorg/projects/73`). Can be omitted if configured in safe-outputs. |
 | `body` | Markdown | Status update content with campaign summary, findings, and next steps |
 
 #### Optional Fields
diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json
index b620458f66b..ece57db1c71 100644
--- a/pkg/parser/schemas/main_workflow_schema.json
+++ b/pkg/parser/schemas/main_workflow_schema.json
@@ -4231,6 +4231,12 @@
                   "$ref": "#/$defs/github_token",
                   "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
                 },
+                "project": {
+                  "type": "string",
+                  "description": "Default project URL for update-project operations. When specified, safe output messages can omit the project field and will use this URL by default. Must be a valid GitHub Projects v2 URL. Overridden by explicit project field in safe output messages.",
+                  "pattern": "^https://github\\.com/(users|orgs)/([^/]+|<[A-Z_]+>)/projects/(\\d+|<[A-Z_]+>)$",
+                  "examples": ["https://github.com/orgs/myorg/projects/123", "https://github.com/users/username/projects/456"]
+                },
                 "views": {
                   "type": "array",
                   "description": "Optional array of project views to create. Each view must have a name and layout. Views are created during project setup.",
@@ -4481,6 +4487,12 @@
                 "github-token": {
                   "$ref": "#/$defs/github_token",
                   "description": "GitHub token to use for this specific output type. Overrides global github-token if specified. Must have Projects: Read+Write permission."
+                },
+                "project": {
+                  "type": "string",
+                  "description": "Default project URL for status update operations. When specified, safe output messages can omit the project field and will use this URL by default. Must be a valid GitHub Projects v2 URL. Overridden by explicit project field in safe output messages.",
+                  "pattern": "^https://github\\.com/(users|orgs)/([^/]+|<[A-Z_]+>)/projects/(\\d+|<[A-Z_]+>)$",
+                  "examples": ["https://github.com/orgs/myorg/projects/123", "https://github.com/users/username/projects/456"]
                 }
               },
               "additionalProperties": false,
diff --git a/pkg/workflow/compiler_safe_outputs_steps.go b/pkg/workflow/compiler_safe_outputs_steps.go
index eba2a8fcf76..226b9840a75 100644
--- a/pkg/workflow/compiler_safe_outputs_steps.go
+++ b/pkg/workflow/compiler_safe_outputs_steps.go
@@ -239,13 +239,26 @@ func (c *Compiler) buildProjectHandlerManagerStep(data *WorkflowData) []string {
 	token := getEffectiveProjectGitHubToken(customToken, data.GitHubToken)
 	steps = append(steps, fmt.Sprintf("          GH_AW_PROJECT_GITHUB_TOKEN: %s\n", token))
 
-	// Add GH_AW_PROJECT_URL if project is configured in frontmatter
+	// Add GH_AW_PROJECT_URL if project is configured in frontmatter or safe-outputs config
 	// This provides a default project URL for update-project and create-project-status-update operations
 	// when target=context (or target not specified). Users can override by setting target=* and
 	// providing an explicit project field in the safe output message.
+	//
+	// Precedence: frontmatter project > update-project.project > create-project-status-update.project
+	var projectURL string
 	if data.ParsedFrontmatter != nil && data.ParsedFrontmatter.Project != nil && data.ParsedFrontmatter.Project.URL != "" {
-		consolidatedSafeOutputsStepsLog.Printf("Adding GH_AW_PROJECT_URL environment variable: %s", data.ParsedFrontmatter.Project.URL)
-		steps = append(steps, fmt.Sprintf("          GH_AW_PROJECT_URL: %q\n", data.ParsedFrontmatter.Project.URL))
+		projectURL = data.ParsedFrontmatter.Project.URL
+		consolidatedSafeOutputsStepsLog.Printf("Using project URL from frontmatter: %s", projectURL)
+	} else if data.SafeOutputs.UpdateProjects != nil && data.SafeOutputs.UpdateProjects.Project != "" {
+		projectURL = data.SafeOutputs.UpdateProjects.Project
+		consolidatedSafeOutputsStepsLog.Printf("Using project URL from update-project config: %s", projectURL)
+	} else if data.SafeOutputs.CreateProjectStatusUpdates != nil && data.SafeOutputs.CreateProjectStatusUpdates.Project != "" {
+		projectURL = data.SafeOutputs.CreateProjectStatusUpdates.Project
+		consolidatedSafeOutputsStepsLog.Printf("Using project URL from create-project-status-update config: %s", projectURL)
+	}
+
+	if projectURL != "" {
+		steps = append(steps, fmt.Sprintf("          GH_AW_PROJECT_URL: %q\n", projectURL))
 	}
 
 	// With section for github-token
diff --git a/pkg/workflow/create_project_status_update.go b/pkg/workflow/create_project_status_update.go
index 5c178b09b5f..f73ca476316 100644
--- a/pkg/workflow/create_project_status_update.go
+++ b/pkg/workflow/create_project_status_update.go
@@ -10,6 +10,7 @@ var createProjectStatusUpdateLog = logger.New("workflow:create_project_status_up
 type CreateProjectStatusUpdateConfig struct {
 	BaseSafeOutputConfig
 	GitHubToken string `yaml:"github-token,omitempty"` // Optional custom GitHub token for project status updates
+	Project     string `yaml:"project,omitempty"`      // Optional default project URL for status updates
 }
 
 // parseCreateProjectStatusUpdateConfig handles create-project-status-update configuration
@@ -29,10 +30,18 @@ func (c *Compiler) parseCreateProjectStatusUpdateConfig(outputMap map[string]any
 					createProjectStatusUpdateLog.Print("Using custom GitHub token for create-project-status-update")
 				}
 			}
+
+			// Parse project URL override if specified
+			if project, exists := configMap["project"]; exists {
+				if projectStr, ok := project.(string); ok {
+					config.Project = projectStr
+					createProjectStatusUpdateLog.Printf("Using custom project URL for create-project-status-update: %s", projectStr)
+				}
+			}
 		}
 
-		createProjectStatusUpdateLog.Printf("Parsed create-project-status-update config: max=%d, hasCustomToken=%v",
-			config.Max, config.GitHubToken != "")
+		createProjectStatusUpdateLog.Printf("Parsed create-project-status-update config: max=%d, hasCustomToken=%v, hasCustomProject=%v",
+			config.Max, config.GitHubToken != "", config.Project != "")
 		return config
 	}
 	createProjectStatusUpdateLog.Print("No create-project-status-update configuration found")
diff --git a/pkg/workflow/create_project_status_update_handler_config_test.go b/pkg/workflow/create_project_status_update_handler_config_test.go
index e3becc261b5..e12efb58089 100644
--- a/pkg/workflow/create_project_status_update_handler_config_test.go
+++ b/pkg/workflow/create_project_status_update_handler_config_test.go
@@ -201,3 +201,40 @@ Test workflow
 	assert.Contains(t, projectConfigJSON, `"create_project_status_update":{"max":2}`,
 		"Expected create_project_status_update with max:2 in project handler config")
 }
+
+// TestCreateProjectStatusUpdateWithProjectURLConfig verifies that the project URL configuration
+// is properly set as an environment variable when configured in safe-outputs
+func TestCreateProjectStatusUpdateWithProjectURLConfig(t *testing.T) {
+	tmpDir := testutil.TempDir(t, "handler-config-test")
+
+	testContent := `---
+name: Test Create Project Status Update with Project URL
+on: workflow_dispatch
+engine: copilot
+safe-outputs:
+  create-project-status-update:
+    max: 1
+    project: "https://github.com/orgs/nonexistent-test-org-67890/projects/88888"
+---
+
+Test workflow
+`
+
+	mdFile := filepath.Join(tmpDir, "test-workflow.md")
+	err := os.WriteFile(mdFile, []byte(testContent), 0600)
+	require.NoError(t, err, "Failed to write test markdown file")
+
+	compiler := NewCompiler()
+	err = compiler.CompileWorkflow(mdFile)
+	require.NoError(t, err, "Failed to compile workflow")
+
+	lockFile := filepath.Join(tmpDir, "test-workflow.lock.yml")
+	compiledContent, err := os.ReadFile(lockFile)
+	require.NoError(t, err, "Failed to read compiled output")
+
+	compiledStr := string(compiledContent)
+
+	// Verify GH_AW_PROJECT_URL environment variable is set
+	require.Contains(t, compiledStr, "GH_AW_PROJECT_URL:", "Expected GH_AW_PROJECT_URL environment variable")
+	require.Contains(t, compiledStr, "https://github.com/orgs/nonexistent-test-org-67890/projects/88888", "Expected project URL in environment variable")
+}
diff --git a/pkg/workflow/data/action_pins.json b/pkg/workflow/data/action_pins.json
index 80130e4a1a7..2db5179bb99 100644
--- a/pkg/workflow/data/action_pins.json
+++ b/pkg/workflow/data/action_pins.json
@@ -100,7 +100,7 @@
       "version": "v5.6.0",
       "sha": "a26af69be951a213d495a4c3e4e4022e16d87065"
     },
-    "actions/upload-artifact@v4": {
+    "actions/upload-artifact@v4.6.2": {
       "repo": "actions/upload-artifact",
       "version": "v4.6.2",
       "sha": "ea165f8d65b6e75b540449e92b4886f43607fa02"
diff --git a/pkg/workflow/project_safe_outputs.go b/pkg/workflow/project_safe_outputs.go
index 9499553ee42..c7b46075303 100644
--- a/pkg/workflow/project_safe_outputs.go
+++ b/pkg/workflow/project_safe_outputs.go
@@ -65,6 +65,12 @@ func (c *Compiler) applyProjectSafeOutputs(frontmatter map[string]any, existingS
 		projectSafeOutputsLog.Print("update-project already configured, preserving existing configuration")
 	}
 
+	// Enforce top-level project URL on update-project (security: stay within scope)
+	if safeOutputs.UpdateProjects != nil {
+		safeOutputs.UpdateProjects.Project = projectURL
+		projectSafeOutputsLog.Printf("Enforcing top-level project URL on update-project: %s", projectURL)
+	}
+
 	// Configure create-project-status-update if not already configured
 	if safeOutputs.CreateProjectStatusUpdates == nil {
 		projectSafeOutputsLog.Printf("Adding create-project-status-update safe-output (max: %d)", maxStatusUpdates)
@@ -77,5 +83,11 @@ func (c *Compiler) applyProjectSafeOutputs(frontmatter map[string]any, existingS
 		projectSafeOutputsLog.Print("create-project-status-update already configured, preserving existing configuration")
 	}
 
+	// Enforce top-level project URL on create-project-status-update (security: stay within scope)
+	if safeOutputs.CreateProjectStatusUpdates != nil {
+		safeOutputs.CreateProjectStatusUpdates.Project = projectURL
+		projectSafeOutputsLog.Printf("Enforcing top-level project URL on create-project-status-update: %s", projectURL)
+	}
+
 	return safeOutputs
 }
diff --git a/pkg/workflow/project_safe_outputs_test.go b/pkg/workflow/project_safe_outputs_test.go
index 168923be9c2..f4e20f5503f 100644
--- a/pkg/workflow/project_safe_outputs_test.go
+++ b/pkg/workflow/project_safe_outputs_test.go
@@ -119,3 +119,63 @@ func TestProjectConfigIntegration(t *testing.T) {
 	// Check create-project-status-update configuration
 	assert.Equal(t, 1, result.CreateProjectStatusUpdates.Max, "CreateProjectStatusUpdates max should match")
 }
+
+func TestApplyProjectSafeOutputsEnforcesProjectURL(t *testing.T) {
+	compiler := NewCompiler()
+	projectURL := "https://github.com/orgs/nonexistent-test-org-99999/projects/99999"
+
+	tests := []struct {
+		name                string
+		frontmatter         map[string]any
+		existingSafeOutputs *SafeOutputsConfig
+		expectEnforcement   bool
+	}{
+		{
+			name: "enforces project URL on newly created configs",
+			frontmatter: map[string]any{
+				"project": projectURL,
+			},
+			existingSafeOutputs: nil,
+			expectEnforcement:   true,
+		},
+		{
+			name: "enforces project URL on existing configs",
+			frontmatter: map[string]any{
+				"project": projectURL,
+			},
+			existingSafeOutputs: &SafeOutputsConfig{
+				UpdateProjects: &UpdateProjectConfig{
+					BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 25},
+					Project:              "https://github.com/orgs/another-fake-org-88888/projects/88888", // Should be overridden
+				},
+				CreateProjectStatusUpdates: &CreateProjectStatusUpdateConfig{
+					BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 3},
+					Project:              "https://github.com/orgs/another-fake-org-88888/projects/88888", // Should be overridden
+				},
+			},
+			expectEnforcement: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := compiler.applyProjectSafeOutputs(tt.frontmatter, tt.existingSafeOutputs)
+
+			if tt.expectEnforcement {
+				require.NotNil(t, result, "Safe outputs should be created")
+
+				// Verify update-project has enforced project URL
+				if result.UpdateProjects != nil {
+					assert.Equal(t, projectURL, result.UpdateProjects.Project,
+						"update-project.project should be enforced to top-level project URL")
+				}
+
+				// Verify create-project-status-update has enforced project URL
+				if result.CreateProjectStatusUpdates != nil {
+					assert.Equal(t, projectURL, result.CreateProjectStatusUpdates.Project,
+						"create-project-status-update.project should be enforced to top-level project URL")
+				}
+			}
+		})
+	}
+}
diff --git a/pkg/workflow/update_project.go b/pkg/workflow/update_project.go
index 6c3a83e86de..4692c787bd0 100644
--- a/pkg/workflow/update_project.go
+++ b/pkg/workflow/update_project.go
@@ -25,6 +25,7 @@ type ProjectFieldDefinition struct {
 type UpdateProjectConfig struct {
 	BaseSafeOutputConfig `yaml:",inline"`
 	GitHubToken          string                   `yaml:"github-token,omitempty"`
+	Project              string                   `yaml:"project,omitempty"` // Default project URL for operations
 	Views                []ProjectView            `yaml:"views,omitempty"`
 	FieldDefinitions     []ProjectFieldDefinition `yaml:"field-definitions,omitempty" json:"field_definitions,omitempty"`
 }
@@ -48,6 +49,14 @@ func (c *Compiler) parseUpdateProjectConfig(outputMap map[string]any) *UpdatePro
 				}
 			}
 
+			// Parse project URL override if specified
+			if project, exists := configMap["project"]; exists {
+				if projectStr, ok := project.(string); ok {
+					updateProjectConfig.Project = projectStr
+					updateProjectLog.Printf("Using custom project URL for update-project: %s", projectStr)
+				}
+			}
+
 			// Parse views if specified
 			if viewsData, exists := configMap["views"]; exists {
 				if viewsList, ok := viewsData.([]any); ok {
@@ -155,8 +164,8 @@ func (c *Compiler) parseUpdateProjectConfig(outputMap map[string]any) *UpdatePro
 			}
 		}
 
-		updateProjectLog.Printf("Parsed update-project config: max=%d, hasCustomToken=%v, viewCount=%d, fieldDefinitionCount=%d",
-			updateProjectConfig.Max, updateProjectConfig.GitHubToken != "", len(updateProjectConfig.Views), len(updateProjectConfig.FieldDefinitions))
+		updateProjectLog.Printf("Parsed update-project config: max=%d, hasCustomToken=%v, hasCustomProject=%v, viewCount=%d, fieldDefinitionCount=%d",
+			updateProjectConfig.Max, updateProjectConfig.GitHubToken != "", updateProjectConfig.Project != "", len(updateProjectConfig.Views), len(updateProjectConfig.FieldDefinitions))
 		return updateProjectConfig
 	}
 	updateProjectLog.Print("No update-project configuration found")
diff --git a/pkg/workflow/update_project_handler_config_test.go b/pkg/workflow/update_project_handler_config_test.go
index 742273a1583..d7b35f7c9c1 100644
--- a/pkg/workflow/update_project_handler_config_test.go
+++ b/pkg/workflow/update_project_handler_config_test.go
@@ -53,3 +53,38 @@ Test workflow
 		"Expected field definitions in update_project handler config",
 	)
 }
+
+func TestUpdateProjectWithProjectURLConfig(t *testing.T) {
+	tmpDir := testutil.TempDir(t, "handler-config-test")
+
+	testContent := `---
+name: Test Update Project with Project URL
+on: workflow_dispatch
+engine: copilot
+safe-outputs:
+  update-project:
+    max: 5
+    project: "https://github.com/orgs/nonexistent-test-org-12345/projects/99999"
+---
+
+Test workflow
+`
+
+	mdFile := filepath.Join(tmpDir, "test-workflow.md")
+	err := os.WriteFile(mdFile, []byte(testContent), 0600)
+	require.NoError(t, err, "Failed to write test markdown file")
+
+	compiler := NewCompiler()
+	err = compiler.CompileWorkflow(mdFile)
+	require.NoError(t, err, "Failed to compile workflow")
+
+	lockFile := filepath.Join(tmpDir, "test-workflow.lock.yml")
+	compiledContent, err := os.ReadFile(lockFile)
+	require.NoError(t, err, "Failed to read compiled output")
+
+	compiledStr := string(compiledContent)
+
+	// Verify GH_AW_PROJECT_URL environment variable is set
+	require.Contains(t, compiledStr, "GH_AW_PROJECT_URL:", "Expected GH_AW_PROJECT_URL environment variable")
+	require.Contains(t, compiledStr, "https://github.com/orgs/nonexistent-test-org-12345/projects/99999", "Expected project URL in environment variable")
+}
