Skip to content

Commit 309f79f

Browse files
Copilotpelikhan
andauthored
Move stop-time safety checks to dedicated job with actions:write permission (#1156)
* Initial plan * Move stop-time safety checks to dedicated job with actions:write permission - Created buildStopTimeCheckJob() function to create dedicated job for stop-time checks - Updated buildJobs() to create stop_time_check job before main job - Main job now depends on stop_time_check job (if it exists) - stop_time_check job has actions:write permission for gh workflow disable - stop_time_check job depends on activation job (if it exists) - Removed generateStopTimeChecks() call from main job steps - Added comprehensive tests for stop-time check job creation - All tests passing and linter clean Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Remove agent job dependency on stop_time_check job Per user feedback, the agent job should not have a "needs" dependency on the stop_time_check job. Both jobs now run independently (or both depend on activation if it exists), allowing the agent to start without waiting for stop-time checks. Changes: - Removed stopTimeCheckJobCreated parameter from buildMainJob - Reverted buildMainJob to only depend on activation job (if exists) - Updated tests to reflect that agent no longer depends on stop_time_check - stop_time_check job still created and runs, just not as blocking dependency Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Format, lint, and recompile workflows - Formatted JavaScript files with prettier (indentation fixes) - All lint checks passing - Recompiled all workflow files to include formatting updates Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Add stop-time test workflows and update dev.md with stop-after - Added test-claude-stop-time.md and test-copilot-stop-time.md test workflows - Both test workflows use stop-after configuration (+24h and +48h respectively) - Updated dev.md with stop-after: "2026-01-01 00:00:00" as requested - Recompiled all workflows to include stop_time_check jobs - Test workflows verify stop-time safety check job creation with actions:write permission Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Remove test workflow lock files, format, lint, and recompile - Removed pkg/cli/workflows/test-claude-stop-time.lock.yml from git tracking - Removed pkg/cli/workflows/test-copilot-stop-time.lock.yml from git tracking - Lock files are now ignored by .gitignore (only .md files tracked for tests) - Formatted JavaScript files with prettier - All lint checks passing - Recompiled all workflows Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent 05ead6d commit 309f79f

File tree

7 files changed

+330
-37
lines changed

7 files changed

+330
-37
lines changed

.github/workflows/dev.lock.yml

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/dev.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- copilot*
7+
stop-after: "2026-01-01 00:00:00"
78
engine: copilot
89
tools:
910
github:
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
on:
3+
workflow_dispatch:
4+
stop-after: "+24h"
5+
permissions:
6+
contents: read
7+
engine: claude
8+
---
9+
10+
# Test Claude Stop-Time
11+
12+
This is a test workflow to verify stop-time safety checks with Claude engine.
13+
14+
The workflow has a stop-after configuration that should create a dedicated stop_time_check job
15+
with actions:write permission to disable the workflow if the deadline is reached.
16+
17+
Please analyze the current repository state and provide a brief summary.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
on:
3+
workflow_dispatch:
4+
stop-after: "+48h"
5+
permissions:
6+
contents: read
7+
engine: copilot
8+
---
9+
10+
# Test Copilot Stop-Time
11+
12+
This is a test workflow to verify stop-time safety checks with Copilot engine.
13+
14+
The workflow has a stop-after configuration that should create a dedicated stop_time_check job
15+
with actions:write permission to disable the workflow if the deadline is reached.
16+
17+
Please analyze the current repository state and provide a brief summary.

pkg/workflow/compiler.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,17 @@ func (c *Compiler) buildJobs(data *WorkflowData, markdownPath string) error {
13291329
}
13301330
}
13311331

1332+
// Build stop-time check job if stop-time is configured
1333+
if data.StopTime != "" {
1334+
stopTimeCheckJob, err := c.buildStopTimeCheckJob(data, activationJobCreated)
1335+
if err != nil {
1336+
return fmt.Errorf("failed to build stop_time_check job: %w", err)
1337+
}
1338+
if err := c.jobManager.AddJob(stopTimeCheckJob); err != nil {
1339+
return fmt.Errorf("failed to add stop_time_check job: %w", err)
1340+
}
1341+
}
1342+
13321343
// Build main workflow job
13331344
mainJob, err := c.buildMainJob(data, activationJobCreated)
13341345
if err != nil {
@@ -1720,8 +1731,8 @@ func (c *Compiler) generateMainJobSteps(yaml *strings.Builder, data *WorkflowDat
17201731
// Add MCP setup
17211732
c.generateMCPSetup(yaml, data.Tools, engine, data)
17221733

1723-
// Add safety checks before executing agentic tools
1724-
c.generateStopTimeChecks(yaml, data)
1734+
// Stop-time safety checks are now handled by a dedicated job (stop_time_check)
1735+
// No longer generated in the main job steps
17251736

17261737
// Add prompt creation step
17271738
c.generatePrompt(yaml, data)

pkg/workflow/stop_after.go

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package workflow
22

33
import (
44
"fmt"
5-
"strings"
65
"time"
76

87
"github.com/githubnext/gh-aw/pkg/console"
@@ -91,48 +90,61 @@ func resolveStopTime(stopTime string, compilationTime time.Time) (string, error)
9190
return parseAbsoluteDateTime(stopTime)
9291
}
9392

94-
// generateStopTimeChecks generates safety checks for stop-time before executing agentic tools
95-
func (c *Compiler) generateStopTimeChecks(yaml *strings.Builder, data *WorkflowData) {
96-
// If no safety settings, skip generating safety checks
93+
// buildStopTimeCheckJob creates the stop-time check job that validates workflow time limits
94+
func (c *Compiler) buildStopTimeCheckJob(data *WorkflowData, activationJobCreated bool) (*Job, error) {
9795
if data.StopTime == "" {
98-
return
96+
return nil, fmt.Errorf("stop-time configuration is required")
9997
}
10098

101-
yaml.WriteString(" - name: Safety checks\n")
102-
yaml.WriteString(" run: |\n")
103-
yaml.WriteString(" set -e\n")
104-
yaml.WriteString(" echo \"Performing safety checks before executing agentic tools...\"\n")
99+
var steps []string
100+
101+
steps = append(steps, " - name: Safety checks\n")
102+
steps = append(steps, " run: |\n")
103+
steps = append(steps, " set -e\n")
104+
steps = append(steps, " echo \"Performing safety checks before executing agentic tools...\"\n")
105105

106106
// Extract workflow name for gh workflow commands
107107
workflowName := data.Name
108-
fmt.Fprintf(yaml, " WORKFLOW_NAME=\"%s\"\n", workflowName)
108+
steps = append(steps, fmt.Sprintf(" WORKFLOW_NAME=\"%s\"\n", workflowName))
109109

110110
// Add stop-time check
111-
if data.StopTime != "" {
112-
yaml.WriteString(" \n")
113-
yaml.WriteString(" # Check stop-time limit\n")
114-
fmt.Fprintf(yaml, " STOP_TIME=\"%s\"\n", data.StopTime)
115-
yaml.WriteString(" echo \"Checking stop-time limit: $STOP_TIME\"\n")
116-
yaml.WriteString(" \n")
117-
yaml.WriteString(" # Convert stop time to epoch seconds\n")
118-
yaml.WriteString(" STOP_EPOCH=$(date -d \"$STOP_TIME\" +%s 2>/dev/null || echo \"invalid\")\n")
119-
yaml.WriteString(" if [ \"$STOP_EPOCH\" = \"invalid\" ]; then\n")
120-
yaml.WriteString(" echo \"Warning: Invalid stop-time format: $STOP_TIME. Expected format: YYYY-MM-DD HH:MM:SS\"\n")
121-
yaml.WriteString(" else\n")
122-
yaml.WriteString(" CURRENT_EPOCH=$(date +%s)\n")
123-
yaml.WriteString(" echo \"Current time: $(date)\"\n")
124-
yaml.WriteString(" echo \"Stop time: $STOP_TIME\"\n")
125-
yaml.WriteString(" \n")
126-
yaml.WriteString(" if [ \"$CURRENT_EPOCH\" -ge \"$STOP_EPOCH\" ]; then\n")
127-
yaml.WriteString(" echo \"Stop time reached. Attempting to disable workflow to prevent cost overrun, then exiting.\"\n")
128-
yaml.WriteString(" gh workflow disable \"$WORKFLOW_NAME\"\n")
129-
yaml.WriteString(" echo \"Workflow disabled. No future runs will be triggered.\"\n")
130-
yaml.WriteString(" exit 1\n")
131-
yaml.WriteString(" fi\n")
132-
yaml.WriteString(" fi\n")
111+
steps = append(steps, " \n")
112+
steps = append(steps, " # Check stop-time limit\n")
113+
steps = append(steps, fmt.Sprintf(" STOP_TIME=\"%s\"\n", data.StopTime))
114+
steps = append(steps, " echo \"Checking stop-time limit: $STOP_TIME\"\n")
115+
steps = append(steps, " \n")
116+
steps = append(steps, " # Convert stop time to epoch seconds\n")
117+
steps = append(steps, " STOP_EPOCH=$(date -d \"$STOP_TIME\" +%s 2>/dev/null || echo \"invalid\")\n")
118+
steps = append(steps, " if [ \"$STOP_EPOCH\" = \"invalid\" ]; then\n")
119+
steps = append(steps, " echo \"Warning: Invalid stop-time format: $STOP_TIME. Expected format: YYYY-MM-DD HH:MM:SS\"\n")
120+
steps = append(steps, " else\n")
121+
steps = append(steps, " CURRENT_EPOCH=$(date +%s)\n")
122+
steps = append(steps, " echo \"Current time: $(date)\"\n")
123+
steps = append(steps, " echo \"Stop time: $STOP_TIME\"\n")
124+
steps = append(steps, " \n")
125+
steps = append(steps, " if [ \"$CURRENT_EPOCH\" -ge \"$STOP_EPOCH\" ]; then\n")
126+
steps = append(steps, " echo \"Stop time reached. Attempting to disable workflow to prevent cost overrun, then exiting.\"\n")
127+
steps = append(steps, " gh workflow disable \"$WORKFLOW_NAME\"\n")
128+
steps = append(steps, " echo \"Workflow disabled. No future runs will be triggered.\"\n")
129+
steps = append(steps, " exit 1\n")
130+
steps = append(steps, " fi\n")
131+
steps = append(steps, " fi\n")
132+
steps = append(steps, " echo \"All safety checks passed. Proceeding with agentic tool execution.\"\n")
133+
steps = append(steps, " env:\n")
134+
steps = append(steps, " GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n")
135+
136+
var depends []string
137+
if activationJobCreated {
138+
depends = []string{"activation"} // Depend on the activation job only if it exists
139+
}
140+
141+
job := &Job{
142+
Name: "stop_time_check",
143+
RunsOn: "runs-on: ubuntu-latest",
144+
Permissions: "permissions:\n actions: write # Required for gh workflow disable",
145+
Steps: steps,
146+
Needs: depends,
133147
}
134148

135-
yaml.WriteString(" echo \"All safety checks passed. Proceeding with agentic tool execution.\"\n")
136-
yaml.WriteString(" env:\n")
137-
yaml.WriteString(" GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n")
149+
return job, nil
138150
}

0 commit comments

Comments
 (0)