Skip to content

refactor(agent): unify regular and upgrade step execution - #407

Open
rice-riley wants to merge 1 commit into
agent-hostfs-219from
agent-step-execution-219
Open

refactor(agent): unify regular and upgrade step execution#407
rice-riley wants to merge 1 commit into
agent-hostfs-219from
agent-step-execution-219

Conversation

@rice-riley

Copy link
Copy Markdown
Member

Description

Consolidates regular and upgrade steps onto one command-execution implementation while keeping their wire types and stage-specific behavior explicit.

  • Extracts shared construction, defaults, execution, fingerprinting, and environment handling.
  • Adds immutable version injection through the Step interface for upgrade-stage orchestration.
  • Resolves non-host execution paths through the mounted host root while preserving host chroot behavior.
  • Keeps configured upgrade arguments separate from runtime previous/current version values.
  • Expands regular, upgrade, shared, and Linux-specific execution coverage.

Depends on #406.
Part of #219

Checklist

  • I am familiar with the Contributing Guidelines.
  • My commits are signed off (git commit -s) per the DCO.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@github-actions github-actions Bot added component/operator Skyhook operator (controller-manager) component/agent Skyhook agent (package executor) component/ci CI workflows, GitHub Actions, and repo tooling labels Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change centralizes command-step options, defaults, execution, fingerprinting, serialization, and environment resolution in shared helpers. RegularStep uses the shared implementation and supports runtime versions. UpgradeStep stores explicit fields, requires versions during execution, and excludes runtime versions from serialized configuration. Step decoding and tests were updated. Execution documentation now describes host-relative paths.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: lockwobr, ayuskauskas

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: unifying regular and upgrade step execution.
Description check ✅ Passed The description directly explains the shared execution refactor, version handling, path resolution, and expanded tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent-step-execution-219

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
agent/go/internal/step/step.go (1)

74-90: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the OnHost default when decoding.

newStepOptions defaults OnHost to true. Decode creates zero-value steps, and applyStepDefaults does not set boolean fields. A payload without on_host therefore runs as non-host, while the equivalent constructed step runs in the host chroot.

Initialize both decode targets with OnHost: true before unmarshalling. JSON will still preserve an explicit false. Add a regression test for omitted and explicit on_host.

Proposed fix
 if probe.UpgradeStep {
-	var u UpgradeStep
+	u := UpgradeStep{OnHost: true}
 	if err := json.Unmarshal(data, &u); err != nil {
 		return nil, fmt.Errorf("decode upgrade step: %w", err)
 	}
@@
-	var regular RegularStep
+	regular := RegularStep{OnHost: true}
 	if err := json.Unmarshal(data, &regular); err != nil {
 		return nil, fmt.Errorf("decode step: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/step.go` around lines 74 - 90, Update Decode’s
UpgradeStep and RegularStep initialization to set OnHost: true before
json.Unmarshal, allowing an explicit JSON false to override the default. Add
regression coverage for both omitted on_host (true) and explicit on_host:false
(false), while preserving existing validation and default application.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent/go/internal/step/shared.go`:
- Around line 204-238: Update step fingerprint generation around stepFingerprint
and its callers to include the runtime version pair used by
RegularStep.WithVersions and UpgradeStep.Run. Add the versions to the serialized
fingerprint payload and pass them from both Fingerprint implementations,
preserving distinct completion flags for different transitions to the same
package version.

---

Outside diff comments:
In `@agent/go/internal/step/step.go`:
- Around line 74-90: Update Decode’s UpgradeStep and RegularStep initialization
to set OnHost: true before json.Unmarshal, allowing an explicit JSON false to
override the default. Add regression coverage for both omitted on_host (true)
and explicit on_host:false (false), while preserving existing validation and
default application.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: e303c3d0-0d57-426b-9b36-0c7b32377165

📥 Commits

Reviewing files that changed from the base of the PR and between fe8a945 and 92c0586.

📒 Files selected for processing (11)
  • agent/go/internal/config/validate_test.go
  • agent/go/internal/execution/execution.go
  • agent/go/internal/step/regular_step.go
  • agent/go/internal/step/regular_step_test.go
  • agent/go/internal/step/shared.go
  • agent/go/internal/step/shared_linux_test.go
  • agent/go/internal/step/shared_test.go
  • agent/go/internal/step/step.go
  • agent/go/internal/step/step_test.go
  • agent/go/internal/step/upgrade_step.go
  • agent/go/internal/step/upgrade_step_test.go

Comment thread agent/go/internal/step/shared.go
@rice-riley
rice-riley force-pushed the agent-step-execution-219 branch from 92c0586 to 6105106 Compare August 4, 2026 19:55
@rice-riley
rice-riley requested a review from a team August 4, 2026 19:55

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent/go/internal/step/shared.go`:
- Around line 251-274: Update decoding in the step types and the applyDefaults
flow so omitted on_host fields retain the constructor default of true while
explicitly decoded false values remain false. Track on_host field presence
during Decode before applying defaults, initialize OnHost only when absent, and
add decode coverage for both step types covering omitted and explicit false
values.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: ca6fe0ef-fee7-4c02-a3c2-3d361f7f22ef

📥 Commits

Reviewing files that changed from the base of the PR and between 92c0586 and 6105106.

📒 Files selected for processing (11)
  • agent/go/internal/config/validate_test.go
  • agent/go/internal/execution/execution.go
  • agent/go/internal/step/regular_step.go
  • agent/go/internal/step/regular_step_test.go
  • agent/go/internal/step/shared.go
  • agent/go/internal/step/shared_linux_test.go
  • agent/go/internal/step/shared_test.go
  • agent/go/internal/step/step.go
  • agent/go/internal/step/step_test.go
  • agent/go/internal/step/upgrade_step.go
  • agent/go/internal/step/upgrade_step_test.go

Comment on lines +251 to +274
func applyStepDefaults(
name *string,
path string,
arguments *[]string,
returncodes *[]command.ExitCode,
environment *map[string]string,
idempotence *Idempotence,
) {
if *name == "" {
*name = path
}
if len(*arguments) == 0 {
*arguments = []string{}
}
if len(*returncodes) == 0 {
*returncodes = []command.ExitCode{command.SuccessExitCode}
}
if *environment == nil {
*environment = map[string]string{}
}
if *idempotence == "" {
*idempotence = Auto
}
}

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve the OnHost default during decoding.

Line 63 makes true the constructor default. Decode calls applyDefaults at Lines 79 and 90 in agent/go/internal/step/step.go, but this helper never initializes OnHost. A payload that omits on_host therefore selects the non-host path at Line 151 instead of chroot execution.

Track on_host field presence during decoding. Set OnHost to true only when the field is absent. Preserve an explicit false. Add decode coverage for both step types.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/shared.go` around lines 251 - 274, Update decoding in
the step types and the applyDefaults flow so omitted on_host fields retain the
constructor default of true while explicitly decoded false values remain false.
Track on_host field presence during Decode before applying defaults, initialize
OnHost only when absent, and add decode coverage for both step types covering
omitted and explicit false values.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent/go/internal/step/upgrade_step_test.go`:
- Around line 116-144: Make this test platform-independent by removing the
/bin/sh script and using the existing
prepareStepTestExecutable/runStepTestHelper mechanism. Extend the inspect action
in runStepTestHelper to print PREVIOUS_VERSION and CURRENT_VERSION, then
construct the upgrade step with that helper and assert its arguments and
environment output while preserving the existing encoding assertions.

In `@agent/go/internal/step/upgrade_step.go`:
- Around line 84-102: In the Run method of UpgradeStep, extend the validation
after the nil check on s.versions to also verify that both s.versions.previous
and s.versions.current are non-empty strings. Return execution.StatusFailed with
an appropriate error message if either version string is empty, rejecting the
step before it proceeds to runStep. This ensures empty version values are caught
defensively rather than being passed as arguments or environment variables to
the script.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 9890055d-0c42-42a4-bfdc-7eb382a1d013

📥 Commits

Reviewing files that changed from the base of the PR and between 629de6c and 6105106.

📒 Files selected for processing (11)
  • agent/go/internal/config/validate_test.go
  • agent/go/internal/execution/execution.go
  • agent/go/internal/step/regular_step.go
  • agent/go/internal/step/regular_step_test.go
  • agent/go/internal/step/shared.go
  • agent/go/internal/step/shared_linux_test.go
  • agent/go/internal/step/shared_test.go
  • agent/go/internal/step/step.go
  • agent/go/internal/step/step_test.go
  • agent/go/internal/step/upgrade_step.go
  • agent/go/internal/step/upgrade_step_test.go

Comment thread agent/go/internal/step/upgrade_step_test.go
Comment on lines +84 to +102
func (s UpgradeStep) Run(ctx context.Context, config execution.Config) (execution.Status, error) {
if err := s.Validate(); err != nil {
return execution.StatusFailed, fmt.Errorf("upgrade step validation failed: %w", err)
}
return u.RegularStep.Run(ctx, config)
if s.versions == nil {
return execution.StatusFailed, errors.New("running upgrade step: versions were not provided")
}
s.applyDefaults()
return runStep(
ctx,
config,
s.ScriptPath,
[]string{s.versions.previous, s.versions.current},
s.Returncodes,
s.OnHost,
s.Env,
s.versions,
)
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find every WithVersions call site and the values it supplies.
rg -n -C 10 --type go 'WithVersions\(' agent/go

# Find how the orchestration derives previous and current package versions.
rg -n -C 8 --type go -i 'previousVersion|currentVersion|PreviousVersion|unknown' agent/go --glob '!**/*_test.go'

Repository: NVIDIA/nodewright

Length of output: 25863


🏁 Script executed:

# Find where upgrade steps have WithVersions called on them
rg -n -C 5 'WithVersions\(' agent/go --type go --glob '!**/*_test.go' | head -80

# Find the orchestration code that manages upgrade steps
fd -e go -path '*/agent/go/*' | xargs grep -l 'UpgradeStep' | grep -v test

# Look for where upgrade step versions come from
rg -n -C 3 'upgrade.*step' agent/go --type go -i | grep -i 'version\|history'

Repository: NVIDIA/nodewright

Length of output: 4648


🏁 Script executed:

# Find the stage execution code that calls WithVersions with versions from history
rg -n 'WithVersions' agent/go --type go -B 5 -A 2 | grep -v test

# Look for code that reads from history and uses upgrade steps
rg -n -C 8 'history\.' agent/go/internal --type go | grep -A 8 -B 8 'WithVersions\|\.Read\|Versions'

# Search for the main orchestration entry point
fd -e go -path '*/agent/go/internal/*' ! -path '*/test*' | xargs grep -l 'Execute\|Run.*stage' | head -5

Repository: NVIDIA/nodewright

Length of output: 2591


🏁 Script executed:

# Search for where WithVersions is actually called in non-test code
rg -n 'WithVersions\(' agent/go -A 3 -B 3 --glob '!*_test.go'

# Look for the stage execution logic
fd -e go -path '*/agent/go/internal/*' ! -name '*_test.go' -type f | xargs grep -l 'stage\|Stage' | head -10

Repository: NVIDIA/nodewright

Length of output: 1929


🏁 Script executed:

# Search more broadly for WithVersions calls
rg 'WithVersions' agent/go -n | grep -v 'func.*WithVersions'

# Look at what files are in the internal directory
ls -la agent/go/internal/

# Search for the executor or orchestration that uses steps
find agent/go/internal -name '*.go' ! -name '*_test.go' -type f | xargs grep -l 'Execute\|ForEach\|Stages'

Repository: NVIDIA/nodewright

Length of output: 1635


🏁 Script executed:

# Look at the stage package structure
ls -la agent/go/internal/stage/

# Look for Execute or main orchestration logic in stage
cat agent/go/internal/stage/*.go | head -200

# Search for any file that processes steps with versions
rg 'step\.' agent/go/internal/stage -A 3 -B 3 -n

Repository: NVIDIA/nodewright

Length of output: 5799


🏁 Script executed:

# Look for the main agent file and executor
find agent/go -name '*.go' ! -name '*_test.go' -type f | xargs grep -l 'Execute\|Main'

# Look for the agent package structure
ls -la agent/go/

# Find where upgrade stage execution happens
rg -n 'Upgrade\|upgrade' agent/go --glob '!*_test.go' | grep -i 'execute\|run\|withversion' | head -20

Repository: NVIDIA/nodewright

Length of output: 738


🏁 Script executed:

# Look at the cmd directory for the main entry point
ls -la agent/go/cmd/

# Check if there's a main file
find agent/go/cmd -name '*.go' -type f | head -10

# Look for executor or orchestration logic by searching for "step" usage
rg -n '\.WithVersions\|ForEach\|for.*step' agent/go --glob '!*_test.go' -A 3 -B 3

Repository: NVIDIA/nodewright

Length of output: 349


🏁 Script executed:

# Look at the main entry point
cat agent/go/cmd/agent/main.go

# Check if there's Python code that calls the Go agent
find . -name '*.py' -type f | xargs grep -l 'WithVersions\|upgrade.*step' 2>/dev/null | head -5

# Search for Python code related to upgrade
find . -path '*/agent/python*' -name '*.py' -type f | head -10

Repository: NVIDIA/nodewright

Length of output: 1497


🏁 Script executed:

# Look at the Python controller that orchestrates the agent
cat ./agent/skyhook-agent/src/skyhook_agent/controller.py

# Look at how Python calls upgrade steps
rg -n 'WithVersions\|upgrade' ./agent/skyhook-agent/src -A 3 -B 3

# Check step.py for how versions are handled
cat ./agent/skyhook-agent/src/skyhook_agent/step.py

Repository: NVIDIA/nodewright

Length of output: 37486


🏁 Script executed:

# Look at the specific function that handles versions for upgrade steps
sed -n '200,250p' ./agent/skyhook-agent/src/skyhook_agent/controller.py

# Look at the history data structure and defaults
sed -n '160,195p' ./agent/skyhook-agent/src/skyhook_agent/controller.py

Repository: NVIDIA/nodewright

Length of output: 4196


🏁 Script executed:

# Get the specific function that handles versions for upgrade steps
sed -n '500,570p' ./agent/skyhook-agent/src/skyhook_agent/controller.py

Repository: NVIDIA/nodewright

Length of output: 3322


🏁 Script executed:

# Find the get_or_update_history function which supplies versions to upgrade steps
grep -n "def get_or_update_history" ./agent/skyhook-agent/src/skyhook_agent/controller.py

# Show that function
sed -n '465,510p' ./agent/skyhook-agent/src/skyhook_agent/controller.py

Repository: NVIDIA/nodewright

Length of output: 2219


🏁 Script executed:

# Show the get_or_update_history function in full
sed -n '376,430p' ./agent/skyhook-agent/src/skyhook_agent/controller.py

Repository: NVIDIA/nodewright

Length of output: 2907


🏁 Script executed:

# Show the rest of the get_or_update_history function
sed -n '430,445p' ./agent/skyhook-agent/src/skyhook_agent/controller.py

Repository: NVIDIA/nodewright

Length of output: 915


🏁 Script executed:

# Check the exact behavior when JSON is corrupted - does it re-initialize?
sed -n '405,420p' ./agent/skyhook-agent/src/skyhook_agent/controller.py

Repository: NVIDIA/nodewright

Length of output: 916


Run method should validate that version strings are not empty.

The guard at line 88 only rejects a nil versions pointer. The WithVersions method accepts any string values without validation. Empty strings would be passed to the script as arguments and set as PREVIOUS_VERSION and CURRENT_VERSION environment variables.

The Python orchestration (agent/skyhook-agent/src/skyhook_agent/controller.py, get_or_update_history function) always supplies non-nil, non-empty strings: either actual version strings from the history file, the string "unknown" when no history exists, or the configured package version. However, the Go implementation should enforce this invariant defensively by rejecting empty strings, matching the pattern used by the history package itself (which converts empty CurrentVersion to "unknown").

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/upgrade_step.go` around lines 84 - 102, In the Run
method of UpgradeStep, extend the validation after the nil check on s.versions
to also verify that both s.versions.previous and s.versions.current are
non-empty strings. Return execution.StatusFailed with an appropriate error
message if either version string is empty, rejecting the step before it proceeds
to runStep. This ensures empty version values are caught defensively rather than
being passed as arguments or environment variables to the script.

@rice-riley
rice-riley force-pushed the agent-step-execution-219 branch from 6105106 to cf0ce60 Compare August 4, 2026 20:30
@rice-riley
rice-riley force-pushed the agent-step-execution-219 branch from cf0ce60 to 84eb7e9 Compare August 5, 2026 20:17
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

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.

♻️ Duplicate comments (4)
agent/go/internal/step/upgrade_step.go (1)

84-102: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Run accepts empty version strings.

The guard at Line 88 rejects only a nil versions pointer. WithVersions performs no validation. Empty strings then reach the script as the first two arguments and as PREVIOUS_VERSION and CURRENT_VERSION. The step script cannot distinguish an empty version from a missing one.

Reject empty previous or current values before calling runStep.

🛡️ Proposed guard
 	if s.versions == nil {
 		return execution.StatusFailed, errors.New("running upgrade step: versions were not provided")
 	}
+	if s.versions.previous == "" || s.versions.current == "" {
+		return execution.StatusFailed, errors.New(
+			"running upgrade step: previous and current versions must not be empty",
+		)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/upgrade_step.go` around lines 84 - 102, Update
UpgradeStep.Run’s versions validation to reject nil versions or empty
versions.previous/current values before applyDefaults and runStep; return
StatusFailed with an appropriate validation error while preserving the existing
behavior for valid versions.
agent/go/internal/step/upgrade_step_test.go (1)

116-144: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

This test depends on /bin/sh and carries no build constraint.

The test writes a script with a #!/bin/sh shebang and runs it with WithOnHost(false), so the interpreter must exist on the machine that runs the suite. Every other execution test in this package re-execs the compiled test binary through prepareStepTestExecutable, which keeps the suite portable.

Extend the inspect action in runStepTestHelper in agent/go/internal/step/regular_step_test.go to print PREVIOUS_VERSION and CURRENT_VERSION, then build the upgrade step from the helper executable and assert against that output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/upgrade_step_test.go` around lines 116 - 144, Replace
the shell-script executable setup in the upgrade test with the compiled helper
produced by prepareStepTestExecutable via the inspect action in
runStepTestHelper. Extend that inspect action in runStepTestHelper to print
PREVIOUS_VERSION and CURRENT_VERSION, then build the upgrade step with the
helper executable and preserve assertions for arguments, environment values,
validation, and encoded arguments.
agent/go/internal/step/shared.go (2)

204-238: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

stepFingerprint still excludes the runtime version pair.

UpgradeStep.Fingerprint passes s.Arguments, which the no-arguments invariant keeps empty. RegularStep.WithVersions also leaves the fingerprint inputs unchanged. Two different transitions to the same target version therefore produce the same digest, so any completion-flag or dedup consumer keyed on the fingerprint can skip a step that ran for a different version pair.

If this omission is intentional, state the reason in a comment on stepFingerprint.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/shared.go` around lines 204 - 238, Update
stepFingerprint and its callers, including UpgradeStep.Fingerprint and
RegularStep.WithVersions, to include the runtime version pair in the serialized
fingerprint payload so transitions to the same target from different source
versions produce distinct digests. If the pair is intentionally excluded,
document that decision directly in stepFingerprint instead.

251-274: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

applyStepDefaults still does not preserve the OnHost constructor default.

Line 63 sets onHost: true for constructed steps. Decode in agent/go/internal/step/step.go (Lines 79 and 90) calls applyDefaults, which routes to applyStepDefaults. This helper does not touch OnHost. A wire payload that omits on_host therefore decodes to false and selects the mounted-root path at Line 151 instead of chroot execution.

Track presence of the on_host key during decoding. Set OnHost to true only when the key is absent. Preserve an explicit false. Add decode coverage for both step types.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/shared.go` around lines 251 - 274, Update the
Decode/applyDefaults flow in step.go and applyStepDefaults so decoding tracks
whether the on_host key was present; default OnHost to true only when absent,
while preserving an explicit false value. Add decode coverage for both supported
step types covering omitted and explicitly false on_host values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@agent/go/internal/step/shared.go`:
- Around line 204-238: Update stepFingerprint and its callers, including
UpgradeStep.Fingerprint and RegularStep.WithVersions, to include the runtime
version pair in the serialized fingerprint payload so transitions to the same
target from different source versions produce distinct digests. If the pair is
intentionally excluded, document that decision directly in stepFingerprint
instead.
- Around line 251-274: Update the Decode/applyDefaults flow in step.go and
applyStepDefaults so decoding tracks whether the on_host key was present;
default OnHost to true only when absent, while preserving an explicit false
value. Add decode coverage for both supported step types covering omitted and
explicitly false on_host values.

In `@agent/go/internal/step/upgrade_step_test.go`:
- Around line 116-144: Replace the shell-script executable setup in the upgrade
test with the compiled helper produced by prepareStepTestExecutable via the
inspect action in runStepTestHelper. Extend that inspect action in
runStepTestHelper to print PREVIOUS_VERSION and CURRENT_VERSION, then build the
upgrade step with the helper executable and preserve assertions for arguments,
environment values, validation, and encoded arguments.

In `@agent/go/internal/step/upgrade_step.go`:
- Around line 84-102: Update UpgradeStep.Run’s versions validation to reject nil
versions or empty versions.previous/current values before applyDefaults and
runStep; return StatusFailed with an appropriate validation error while
preserving the existing behavior for valid versions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 76d070e4-9976-41ac-8613-54ae3ed71914

📥 Commits

Reviewing files that changed from the base of the PR and between 46c2bc3 and 84eb7e9.

📒 Files selected for processing (11)
  • agent/go/internal/config/validate_test.go
  • agent/go/internal/execution/execution.go
  • agent/go/internal/step/regular_step.go
  • agent/go/internal/step/regular_step_test.go
  • agent/go/internal/step/shared.go
  • agent/go/internal/step/shared_linux_test.go
  • agent/go/internal/step/shared_test.go
  • agent/go/internal/step/step.go
  • agent/go/internal/step/step_test.go
  • agent/go/internal/step/upgrade_step.go
  • agent/go/internal/step/upgrade_step_test.go

@rice-riley
rice-riley force-pushed the agent-step-execution-219 branch from 84eb7e9 to 3d1b12a Compare August 5, 2026 22:16
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

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.

♻️ Duplicate comments (3)
agent/go/internal/step/shared.go (2)

251-274: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

applyStepDefaults does not restore the OnHost default.

Line 63 sets onHost: true for constructor paths. applyStepDefaults never initializes OnHost, and Decode in agent/go/internal/step/step.go calls applyDefaults on a zero-valued struct. A payload that omits on_host therefore runs the non-host branch at Line 151 instead of chroot execution. Track field presence during decoding and set OnHost to true only when on_host is absent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/shared.go` around lines 251 - 274, The
decoding/defaulting flow around applyStepDefaults and Decode must preserve the
constructor default OnHost=true when the on_host field is omitted. Track whether
on_host was present during decoding, then set OnHost to true only when absent
while preserving explicitly supplied true or false values.

204-238: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

stepFingerprint still omits the runtime version pair.

UpgradeStep.Run builds its command arguments from s.versions, and runStep injects PREVIOUS_VERSION and CURRENT_VERSION into the environment. Neither value reaches stepFingerprint, so two different version transitions to the same package version produce the same digest. Downstream completion-flag paths keyed by fingerprint can then skip a step for the wrong version pair.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/shared.go` around lines 204 - 238, Update
stepFingerprint and its callers to include the runtime version pair in the
fingerprint payload, using the same versions represented by UpgradeStep.Run
arguments and runStep’s PREVIOUS_VERSION and CURRENT_VERSION environment values.
Ensure distinct version transitions produce distinct digests and preserve the
existing JSON hashing behavior.
agent/go/internal/step/upgrade_step.go (1)

84-102: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Run accepts empty version strings.

The guard at Line 88 rejects only a nil versions pointer. WithVersions performs no validation, so empty strings pass through as the first two script arguments and as PREVIOUS_VERSION and CURRENT_VERSION. Reject empty values before execution.

🛡️ Proposed guard
 	if s.versions == nil {
 		return execution.StatusFailed, errors.New("running upgrade step: versions were not provided")
 	}
+	if s.versions.previous == "" || s.versions.current == "" {
+		return execution.StatusFailed, errors.New("running upgrade step: versions must not be empty")
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/upgrade_step.go` around lines 84 - 102, Update
UpgradeStep.Run to validate both s.versions.previous and s.versions.current are
non-empty after the nil check and before applyDefaults or runStep; return
execution.StatusFailed with an appropriate validation error when either value is
empty, while preserving execution for valid versions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@agent/go/internal/step/shared.go`:
- Around line 251-274: The decoding/defaulting flow around applyStepDefaults and
Decode must preserve the constructor default OnHost=true when the on_host field
is omitted. Track whether on_host was present during decoding, then set OnHost
to true only when absent while preserving explicitly supplied true or false
values.
- Around line 204-238: Update stepFingerprint and its callers to include the
runtime version pair in the fingerprint payload, using the same versions
represented by UpgradeStep.Run arguments and runStep’s PREVIOUS_VERSION and
CURRENT_VERSION environment values. Ensure distinct version transitions produce
distinct digests and preserve the existing JSON hashing behavior.

In `@agent/go/internal/step/upgrade_step.go`:
- Around line 84-102: Update UpgradeStep.Run to validate both
s.versions.previous and s.versions.current are non-empty after the nil check and
before applyDefaults or runStep; return execution.StatusFailed with an
appropriate validation error when either value is empty, while preserving
execution for valid versions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 8272699f-93ca-44db-9297-9531d29d1b00

📥 Commits

Reviewing files that changed from the base of the PR and between bfc89e4 and 3d1b12a.

📒 Files selected for processing (11)
  • agent/go/internal/config/validate_test.go
  • agent/go/internal/execution/execution.go
  • agent/go/internal/step/regular_step.go
  • agent/go/internal/step/regular_step_test.go
  • agent/go/internal/step/shared.go
  • agent/go/internal/step/shared_linux_test.go
  • agent/go/internal/step/shared_test.go
  • agent/go/internal/step/step.go
  • agent/go/internal/step/step_test.go
  • agent/go/internal/step/upgrade_step.go
  • agent/go/internal/step/upgrade_step_test.go

@rice-riley
rice-riley force-pushed the agent-step-execution-219 branch from 3d1b12a to 183eec6 Compare August 6, 2026 19:33
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@rice-riley
rice-riley force-pushed the agent-step-execution-219 branch from 183eec6 to 4d68da6 Compare August 6, 2026 19:35

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
agent/go/internal/step/step.go (1)

74-91: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the default OnHost value for omitted fields.

An omitted on_host field and an explicit false both decode to false. This bypasses the constructor default of true. The shared runner then uses the non-chroot path.

Track on_host field presence during decoding. Set OnHost to true only when the field is absent. Preserve explicit false. Add decode coverage for both step types.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/step.go` around lines 74 - 91, Update the step
decoding flow around UpgradeStep and RegularStep to track whether the on_host
field was present, setting OnHost to true only when omitted while preserving an
explicit false. Apply this behavior consistently to both step types and add
decode coverage for omitted and explicitly false on_host values.
♻️ Duplicate comments (2)
agent/go/internal/step/shared.go (1)

204-238: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Include runtime versions in the step fingerprint.

Runtime versions change command execution. The current fingerprint cannot distinguish version transitions that use the same configured step.

  • agent/go/internal/step/shared.go#L204-L238: serialize explicit previous and current runtime-version fields in the fingerprint payload.
  • agent/go/internal/step/regular_step.go#L91-L94: pass s.versions to the shared fingerprint helper.
  • agent/go/internal/step/upgrade_step.go#L104-L107: pass s.versions to the shared fingerprint helper.

Add tests that confirm distinct version pairs produce distinct fingerprints.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/shared.go` around lines 204 - 238, Extend
stepFingerprint in agent/go/internal/step/shared.go (lines 204-238) to serialize
explicit previous and current runtime-version fields in its payload. Update
callers in agent/go/internal/step/regular_step.go (lines 91-94) and
agent/go/internal/step/upgrade_step.go (lines 104-107) to pass s.versions to the
helper, and add tests verifying distinct version pairs produce distinct
fingerprints.
agent/go/internal/step/upgrade_step.go (1)

88-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject empty runtime versions before execution.

The nil check accepts WithVersions("", ""). The command then receives empty positional arguments and empty PREVIOUS_VERSION and CURRENT_VERSION values.

Reject an empty previous or current version before calling runStep. Add coverage for each invalid value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/upgrade_step.go` around lines 88 - 100, Update the
version validation in the upgrade step before runStep so it rejects both empty
s.versions.previous and empty s.versions.current, returning the existing failed
status with an appropriate error. Preserve valid version execution and add
coverage for each empty value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@agent/go/internal/step/step.go`:
- Around line 74-91: Update the step decoding flow around UpgradeStep and
RegularStep to track whether the on_host field was present, setting OnHost to
true only when omitted while preserving an explicit false. Apply this behavior
consistently to both step types and add decode coverage for omitted and
explicitly false on_host values.

---

Duplicate comments:
In `@agent/go/internal/step/shared.go`:
- Around line 204-238: Extend stepFingerprint in
agent/go/internal/step/shared.go (lines 204-238) to serialize explicit previous
and current runtime-version fields in its payload. Update callers in
agent/go/internal/step/regular_step.go (lines 91-94) and
agent/go/internal/step/upgrade_step.go (lines 104-107) to pass s.versions to the
helper, and add tests verifying distinct version pairs produce distinct
fingerprints.

In `@agent/go/internal/step/upgrade_step.go`:
- Around line 88-100: Update the version validation in the upgrade step before
runStep so it rejects both empty s.versions.previous and empty
s.versions.current, returning the existing failed status with an appropriate
error. Preserve valid version execution and add coverage for each empty value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 64cd8913-2cd4-4065-a7b7-c849dc5000ad

📥 Commits

Reviewing files that changed from the base of the PR and between c95d519 and 183eec6.

📒 Files selected for processing (11)
  • agent/go/internal/config/validate_test.go
  • agent/go/internal/execution/execution.go
  • agent/go/internal/step/regular_step.go
  • agent/go/internal/step/regular_step_test.go
  • agent/go/internal/step/shared.go
  • agent/go/internal/step/shared_linux_test.go
  • agent/go/internal/step/shared_test.go
  • agent/go/internal/step/step.go
  • agent/go/internal/step/step_test.go
  • agent/go/internal/step/upgrade_step.go
  • agent/go/internal/step/upgrade_step_test.go

@rice-riley
rice-riley force-pushed the agent-step-execution-219 branch from 4d68da6 to 1b1ffc8 Compare August 7, 2026 15:14
@rice-riley
rice-riley force-pushed the agent-step-execution-219 branch from 1b1ffc8 to a5b9a9b Compare August 7, 2026 15:26
Signed-off-by: Riley Rice <rrice@nvidia.com>
@rice-riley
rice-riley force-pushed the agent-step-execution-219 branch from a5b9a9b to 608c14e Compare August 7, 2026 19:29

@lockwobr lockwobr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Multi-reviewer cross-review

Three independent reviews of this commit plus a targeted integration-impact pass, cross-reviewed to a 2-of-3 consensus, with every confirmed finding sent to a fresh adversarial verifier instructed to refute it. Reviewed against this PR's actual base (0a02c4f5, the agent-hostfs-219 branch), not main, so defects already present in that base are out of scope.

Outcome: twelve candidate findings were raised. Four were refuted during cross-review, four reached consensus and were then adversarially verified, and of those one survived. Six remain contested and need a human call. The integration pass verified fourteen change-list items and found no broken consumers.

Two of the strongest-looking round-1 findings were refuted on the same grounds, worth stating because it is a point in this PR's favour: the non-host execution path is not newly exposed, it was previously broken (the base pointed a container-namespace step at a host-absolute path that does not exist inside a distroless container), and the version strings that appeared to be injectable are constrained by an anchored semver pattern in skyhook-agent-schema.json and so cannot contain the env: prefix that would be needed.

No code was executed at any point in this review; every claim is from static reading at the pinned commit.

Verified with no issues

The integration pass confirmed each of these individually, which is worth recording since the de-embedding could plausibly have broken any of them:

  • The step fingerprint is byte-stable. The pinned flag-filename hash in flags_test.go:80 is unchanged, so existing completion flags on nodes stay valid.
  • The emitted JSON is byte-identical for both step kinds despite UpgradeStep no longer embedding RegularStep.
  • CURRENT_VERSION / PREVIOUS_VERSION match the shipping Python agent in name, value semantics, and argument order (controller.py:434-438).
  • No type outside internal/step implements step.Step, and no composite literal UpgradeStep{RegularStep: ...} survives anywhere.

Contested — raised but not cross-evaluated

agent/README.md:23 — this file still carries the sentence "A Config composes the host root mount, the child-visible step and package directories…", which is verbatim the wording this PR rewrote in agent/go/internal/execution/execution.go:22 to "the host root mount, step and package paths within that host". agent/README.md is untouched by this commit, so the repo now describes execution.Config two contradictory ways. One reviewer read the untouched file as out of scope; the counter-argument is that the contradiction is created by this commit, and this project treats same-PR doc updates as a blocking requirement for behaviour changes. Your call, but it is a one-line fix.

Open questions

These could not be settled without running code, which this review deliberately does not do:

  1. Does regular_step_test.go:196 pass on macOS? It asserts working-directory= against a lexically joined GinkgoT().TempDir(), but the child's environment is replaced wholesale so PWD is absent and os.Getwd falls back to the syscall, which on macOS resolves /var/… to /private/var/…. Presumably fine on Linux CI.
  2. Does golangci-lint accept encodeStep(value any, …) and the six-pointer-parameter applyStepDefaults under agent/go/.golangci.yml?
  3. Worth deciding before the first caller lands: history.Versions declares its fields in the order Current, Previous, while Step.WithVersions(previous, current string) takes them in the opposite order. Both are bare strings, so an orchestrator written as s.WithVersions(v.Current, v.Previous) compiles cleanly and silently swaps them, handing the package author's upgrade script its arguments backwards. Nothing consumes this pairing yet, so it is not a defect today, but it is the most likely place the Go agent will diverge from controller.py:434-438. A named struct, or matching the field order, would remove the hazard entirely.
  4. Relatedly: UpgradeStep.Run hard-fails when versions are absent, but RegularStep.Run does not, while the Python agent sets the version variables on every step in the upgrade stage (controller.py:617-619), not just upgrade steps. An orchestrator that only calls WithVersions on values type-asserting to UpgradeStep would give regular upgrade-stage steps empty version env with no error. Is the asymmetry intended?

)
Expect(err).NotTo(HaveOccurred())
value := NewRegularStep(
status, err := runStep(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This rewrite drops the only coverage that pinned RegularStep.Run forwarding s.OnHost in the true direction.

The base built a real step and went through the public API:

value := NewRegularStep("step-helper", WithArguments(...))
status, err := value.Run(context.Background(), config)

which exercised the default OnHost=true. The head calls the unexported runStep with onHost hard-coded as a literal true, so RegularStep is no longer in the path at all.

An exhaustive sweep of the package confirms nothing else covers it: every .Run( on a step value in regular_step_test.go (lines 176, 219, 239, 249, 261, 288, 304) passes WithOnHost(false); the two NewUpgradeStep("upgrade.sh") specs default to OnHost=true but return early on the Validate/nil-versions guards before reaching runStep; shared_test.go passes false; and no test outside internal/step calls Step.Run at all. So no public Run reaches runStep with onHost=true.

Concretely: hard-coding false in RegularStep.Run would now pass the entire suite. On the base that would have failed this spec, because without the non-host hostfs.Resolve the step would have exec'd the nonexistent /steps/step-helper.

This is live coverage, not theoretical — agent-go-ci.yaml runs the job in an ubuntu:latest container as root, so the chroot spec actually executes.

Two things narrow it: the opposite regression (hard-coding true) is caught by the new regular_step_test.go:195 spec via its step-root=<mountedStepRoot> assertion, and the applyDefaults-clobber variant is impossible because applyStepDefaults takes no onHost parameter. Test-only; no production behaviour change. Keeping one public-API call on the chroot path would close it.

environment = maps.Clone(environment)
}
if versions != nil {
environment[previousVersionEnv] = versions.previous

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Contested — the reviewers split on this one.

The configured environment is cloned just above, then PREVIOUS_VERSION and CURRENT_VERSION are written unconditionally. A package whose config legitimately sets either key runs with values it did not declare, with no error, warning, or validation. There is no reserved-name check in the config loader, and the schema accepts arbitrary env keys.

For treating it as a defect: a package author has no way to discover the collision. Their declared value silently disappears at runtime, and the failure would surface as inexplicable behaviour inside their script rather than as a config error. Rejecting the collision at validation time would be cheap and would fail loudly at the right layer.

Against: this is exactly what the shipping Python agent does (controller.py:434-435 assigns both keys with no collision check), and the same unconditional-override pattern already applies to STEP_ROOT and SKYHOOK_DIR two lines below, both in this file and in the base. Rejecting a CURRENT_VERSION collision while silently stomping a STEP_ROOT collision would arguably be the less consistent design.

If you keep the current behaviour, the reserved names are worth documenting somewhere a package author will actually look.

environment[previousVersionEnv] = versions.previous
environment[currentVersionEnv] = versions.current
}
environment["STEP_ROOT"] = stepRoot

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Contested — raised late in the review, so it carries only one reviewer's position.

STEP_ROOT and SKYHOOK_DIR now carry values from different namespaces depending on on_host. For an on-host step they stay host-absolute, matching the Python agent's docstring (controller.py:290-291, "The path on the host to the root directory of all the steps"). For a non-host step they are rewritten to <rootMount>/… just above and exported here, as the new spec at regular_step_test.go:227-229 asserts.

Within each namespace that is arguably correct, and it is what makes the non-host path work at all. The concern is that it is a package-author-visible contract with no documentation: a script that records $STEP_ROOT or $SKYHOOK_DIR into host state, or passes either to something that later runs on the host, gets a container-prefixed path that is meaningless there.

Neither agent/README.md nor the on_host description in agent/go/internal/config/schemas/v1/step-schema.json (currently just "Run the step on the host or inside the agent") notes the split.

// UpgradeStep runs during the Upgrade and UpgradeCheck modes.
type UpgradeStep struct {
RegularStep
Name string `json:"name"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Contested — the reviewers split on this one.

Dropping the embedded RegularStep leaves two hand-maintained copies of the same nine exported fields, their JSON tags, the unexported versions field, and applyDefaults. Nothing in the type system links them, and both feed the same Decode path.

The reason this is worth more than a style note is that the test intended to guard it has a hole. upgrade_step_test.go:196-209 byte-compares NewRegularStep("shared.sh").Encode() against NewUpgradeStep("shared.sh").Encode(). But under default construction Env is an empty map and RequiresInterrupt is false, and both fields carry omitempty — so neither key appears in either payload. The comparison cannot see them.

Concretely: renaming UpgradeStep's Env tag to json:"environment,omitempty", or deleting RequiresInterrupt from UpgradeStep entirely, leaves the whole suite green while silently dropping those fields on the upgrade branch of Decode. A package's step env or interrupt requirement would be lost at runtime with no signal.

Against: the fields and tags do match today, so this is a future-maintenance risk rather than a present defect.

If the duplication stays, extending that byte-comparison to a step constructed with a non-empty Env and RequiresInterrupt: true would close the hole cheaply.

IdempotenceMode Idempotence `json:"idempotence"`
UpgradeStep bool `json:"upgrade_step"`
Env map[string]string `json:"env,omitempty"`
RequiresInterrupt bool `json:"requires_interrupt,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Contested — the reviewers split on this one.

The base carried a comment immediately above this field recording that RequiresInterrupt round-trips as requires_interrupt, is emitted only when true, and "stays schema-valid because the step schema permits extra properties." It is gone, and the field now stands unexplained here and on UpgradeStep.

The invariant is real, non-obvious, and owned by a different file: agent/go/internal/config/schemas/v1/step-schema.json lists requires_interrupt in neither properties nor required, and omits additionalProperties: false. The field's schema-validity rests entirely on that omission. A future change that tightens the schema, or that "tidies up" the omitempty, has no in-code signal that the two are coupled.

Against: no behaviour changed; this is a deleted explanation rather than a code bug.

This PR deletes a substantial number of explanatory comments. Most of them genuinely were narration and are better gone. This one, and the OnHost seeding comment, are the two that documented constraints living outside the file they annotated.

)

func newStepOptions(path string, opts ...Option) stepOptions {
value := stepOptions{onHost: true}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Contested — the reviewers split on this one.

The base carried two paired comments explaining this seeding: one here ("OnHost is seeded true here because Go's bool has no 'absent' state, so applyDefaults can't tell zero apart from 'explicit false'; WithOnHost(false) overrides this initial value") and a matching note on applyDefaults saying OnHost is intentionally not defaulted. Both are gone, leaving a bare stepOptions{onHost: true} here and an applyStepDefaults that silently has no onHost parameter.

The asymmetry now looks like an oversight, and the obvious "fix" is harmful: adding an onHost default to applyStepDefaults would break WithOnHost(false), because both RegularStep.Run and UpgradeStep.Run call applyDefaults on every run and would flip an explicitly-false OnHost back to true. That silently promotes a container-scoped step to chrooted host execution as root.

Against: no behaviour changed, and existing specs still pin both the explicit-false and default-true cases, so the mistake would be caught.

Given the failure mode, a one-line note on why onHost is absent from applyStepDefaults seems worth keeping.

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

Labels

component/agent Skyhook agent (package executor) component/ci CI workflows, GitHub Actions, and repo tooling component/operator Skyhook operator (controller-manager)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants