Skip to content

🌱 Add skill script execution probe and ADR 0007 - #73

Closed
fabianvf wants to merge 1 commit into
konveyor:mainfrom
fabianvf:skill-exec-probe
Closed

fabianvf wants to merge 1 commit into
konveyor:mainfrom
fabianvf:skill-exec-probe

Conversation

@fabianvf

@fabianvf fabianvf commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Spike work for #70 — what it takes for skills to carry executable content.

What this is

A probe that answers, for a given cluster, whether an agent can stage a script in a writable directory and execute it, and whether that directory sits inside the git worktree the harness commits and pushes. Plus ADR 0007 recording the convention that falls out of it.

Runs standalone — no CRDs, controller, or LLM needed:

KUBECONFIG=... hack/probe/run-probe.sh

It also probes a real Sandbox pod when one exists, so the hand-written and controller-created cases can be compared. Divergence between them would itself be a finding.

What we found

The original worry was that skills mount read-only and possibly noexec, making executable content unusable. That turns out to be mostly the wrong question.

Measured on CRI-O 1.35 / k8s 1.34:

per-mount opts superblock noexec reaches container write execve
/opt/skills/<name> rw,relatime ro,seclabel,... no EROFS allowed
/tmp rw,relatime rw no ok ok

CRI-O does pass ro,noexec,nosuid,nodev to Store().MountImage(), but it then layers an overlay on top and overlayfs doesn't inherit MS_NOEXEC. Read-only is enforced, at the superblock. So execution isn't the constraint — placement is.

And agents don't execute from the mount anyway. They write a script somewhere writable and run it from there. Which raises the question the ADR actually answers: where?

Given a skill that said only "write the script to a file, make it executable, run it", the model wrote to /workspace/repo/verify.sh and left /tmp empty. That's inside the git worktree the harness pushes. The watcher didn't commit it, but only because its extension allowlist happens to exclude .sh — it does include .md, .json, .yaml and .txt, which is exactly what an agent reaches for when writing notes and plans.

ADR 0007 records the convention (stage to /tmp) and argues it belongs in the harness rather than in every SkillCard, since it's a property of the execution environment rather than of any skill.

Notes

The local dev environment used to run this is being kept out of the PR for now.

Summary by CodeRabbit

  • New Features

    • Added diagnostics to verify whether agents can stage and execute scripts safely in writable directories.
    • Added checks for ImageVolume support, mount behavior, execution permissions, and git worktree placement.
    • Added standalone and sandbox probe support with summarized results, remediation guidance, and failure diagnostics.
  • Documentation

    • Documented the script staging convention and updated platform requirements, runtime compatibility, and local development guidance.
    • Added an unreleased enhancement entry describing the new environment probe.

Answers, per cluster, whether an agent can stage a script in a writable
directory and execute it — and whether that directory sits inside the git
worktree the harness commits and pushes.

The original concern was that skills mount read-only and possibly noexec, so
executable content would be unusable. Measured on CRI-O 1.35: the ImageVolume
is read-only at the superblock but noexec does not reach the container, because
CRI-O sets it on the lowerdir and overlayfs does not inherit MS_NOEXEC. So
execution is not the constraint — placement is. Agents do not execute from the
mount anyway; they write a script somewhere writable and run it from there.

Given a skill that said only "write the script to a file and run it", the model
wrote into the git worktree and left /tmp empty. The watcher did not commit it
only because its extension allowlist happens to exclude .sh; it does include
.md, .json, .yaml and .txt. ADR 0007 records the resulting convention and why
it belongs in the harness rather than in each SkillCard.

The probe runs standalone against any cluster — no CRDs, controller or LLM
required — and also probes a real Sandbox pod when one exists, so the two can
be compared.

Refs konveyor#70

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Fabian von Feilitzsch <fabian@fabianism.us>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds ADR 0007 and documentation for staging scripts from read-only ImageVolume skills into writable executable directories. Adds standalone and sandbox Kubernetes probes that collect mount, runtime, git-worktree, and execution results.

Changes

Skill execution staging

Layer / File(s) Summary
Staging convention and platform documentation
.gitignore, README.md, changes/unreleased/70-skill-exec-probe.yaml, docs/adr/0007-skill-script-execution-and-staging.md
Documents read-only skill mounts, /tmp staging, platform requirements, probe outcomes, alternatives, and local-development exclusions.
Container staging and mount probe
hack/probe/probe-pod.yaml, hack/probe/probe.sh
Defines a diagnostic pod and probes writable executable directories, mount properties, skill mounts, git-worktree containment, and final remediation verdicts.
Cluster probe orchestration
hack/probe/run-probe.sh
Collects cluster and ImageVolume gate facts, manages the standalone pod, runs probes in standalone and Sandbox environments, summarizes results, and reports disagreements.

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

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant run-probe.sh
  participant Kubernetes
  participant ProbePod
  participant probe.sh
  Operator->>run-probe.sh: start probe
  run-probe.sh->>Kubernetes: collect cluster facts and ImageVolume gate
  run-probe.sh->>Kubernetes: create probe pod
  Kubernetes-->>ProbePod: start pod
  run-probe.sh->>ProbePod: execute probe.sh
  probe.sh->>probe.sh: test staging and skill mounts
  probe.sh-->>run-probe.sh: return RESULT diagnostics
  run-probe.sh->>Kubernetes: locate optional Sandbox pod
  run-probe.sh->>Kubernetes: execute probe in Sandbox
  run-probe.sh-->>Operator: summarize and compare verdicts
Loading

Possibly related issues

  • konveyor/agentic-controller issue 44: The probe and pod manifest validate the ImageVolume-based skill mounting and /opt/skills layout described by the issue.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, properly prefixed, and clearly summarizes the main change in the PR.
Description check ✅ Passed The description covers the probe, ADR, findings, and notes, and the 🌱 prefix does not require a changelog fragment.
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 unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 2

🧹 Nitpick comments (2)
hack/probe/run-probe.sh (1)

201-255: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add trap-based cleanup so an interrupted run doesn't leak the probe pod.

If the script is interrupted (Ctrl-C) or fails between start_probe_pod and the final cleanup, ${PROBE_POD} is left running since deletion only happens at the tail of main.

♻️ Suggested fix
 main() {
     mkdir -p "${RESULTS_DIR}"
     RAW_OUT="${RESULTS_DIR}/probe-raw.txt"
     : > "${RAW_OUT}"
+
+    if [ "${KEEP}" != "1" ]; then
+        trap 'kube delete pod "${PROBE_POD}" --ignore-not-found --wait=false >/dev/null 2>&1 || true' EXIT
+    fi
🤖 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 `@hack/probe/run-probe.sh` around lines 201 - 255, Add trap-based cleanup for
the probe pod created by start_probe_pod, so interruptions or failures before
main’s final cleanup still delete ${PROBE_POD} unless PROBE_KEEP=1. Reuse the
existing kube delete behavior and ensure the trap is installed before pod
creation while preserving the current normal cleanup and keep-pod behavior.
docs/adr/0007-skill-script-execution-and-staging.md (1)

51-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add language identifiers to fenced code blocks.

markdownlint (MD040) flags these three fences as missing a language tag.

📝 Suggested fix
-```
+```text
 1267 1259 0:180 / /opt/skills/probe-skill rw,relatime - overlay overlay ro,seclabel,lowerdir=...
(apply the same `text` tag to the fences at lines 96 and 119.)
</details>






Also applies to: 96-99, 119-124

<details>
<summary>🤖 Prompt for AI Agents</summary>

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

In @docs/adr/0007-skill-script-execution-and-staging.md around lines 51 - 53,
Update the three fenced code blocks in the ADR, including the blocks around the
mount-output examples, to specify the text language identifier after each
opening fence. Apply the same text tag consistently to the fences near the
reported locations without changing their contents.


</details>

<!-- cr-comment:v1:c1921ccd982d04f42806bf83 -->

_Source: Linters/SAST tools_

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

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 @hack/probe/probe-pod.yaml:

  • Around line 16-45: Add a pod-level securityContext to the imagevolume-probe
    Pod spec that satisfies Kubernetes restricted Pod Security requirements,
    including non-root execution, seccomp RuntimeDefault, and dropping all Linux
    capabilities. Keep the existing container command, mounts, and volumes
    unchanged.

In @README.md:

  • Around line 59-65: Update the ImageVolume prerequisites in README.md at lines
    59-65 to state that Kubernetes 1.33+ has ImageVolume beta enabled but not by
    default, and change the containerd requirement from 2.0+ to 2.1+. Update the
    containerd warning in hack/probe/run-probe.sh at lines 103-105 from 2.0+ to
    2.1+; no other prerequisite changes are needed.

Nitpick comments:
In @docs/adr/0007-skill-script-execution-and-staging.md:

  • Around line 51-53: Update the three fenced code blocks in the ADR, including
    the blocks around the mount-output examples, to specify the text language
    identifier after each opening fence. Apply the same text tag consistently to the
    fences near the reported locations without changing their contents.

In @hack/probe/run-probe.sh:

  • Around line 201-255: Add trap-based cleanup for the probe pod created by
    start_probe_pod, so interruptions or failures before main’s final cleanup still
    delete ${PROBE_POD} unless PROBE_KEEP=1. Reuse the existing kube delete behavior
    and ensure the trap is installed before pod creation while preserving the
    current normal cleanup and keep-pod behavior.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro Plus

**Run ID**: `537f57b2-2a65-449b-a3b0-721aa1d20939`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7acc1371552ffc33120e2e3a92f10d019e809de3 and afec590b34a3680d3b52e302325ad721a3876853.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `.gitignore`
* `README.md`
* `changes/unreleased/70-skill-exec-probe.yaml`
* `docs/adr/0007-skill-script-execution-and-staging.md`
* `hack/probe/probe-pod.yaml`
* `hack/probe/probe.sh`
* `hack/probe/run-probe.sh`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread hack/probe/probe-pod.yaml
Comment on lines +16 to +45
apiVersion: v1
kind: Pod
metadata:
name: imagevolume-probe
labels:
app.kubernetes.io/name: imagevolume-probe
app.kubernetes.io/component: diagnostic
spec:
restartPolicy: Never
# Override the stub entrypoint: we want a bare long-running process, not the
# stub's banner logic, so the probe measures the environment and nothing else.
containers:
- name: agent
image: quay.io/konveyor/agentic-controller-agent:e2e
command: ["sleep", "infinity"]
volumeMounts:
- name: skill
mountPath: /opt/skills/probe-skill
readOnly: true
- name: workspace
mountPath: /workspace
volumes:
# The mount under test. Any skill image works -- we are measuring how the
# kubelet/CRI mounts an ImageVolume, not the contents.
- name: skill
image:
reference: quay.io/konveyor/skills:maven-migration
# Matches the controller's workspace volume (agentrun_controller.go:332).
- name: workspace
emptyDir: {}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Add restricted-compatible securityContext so the probe can schedule on hardened/PSA-restricted clusters.

No securityContext is set, so this pod will fail admission on any namespace enforcing the Kubernetes "restricted" Pod Security Standard — exactly the kind of hardened cluster ADR 0007 calls out as the one scenario ("BLOCKED_NO_EXEC_SURFACE") that would need re-testing.

🔒 Suggested fix
 spec:
   restartPolicy: Never
+  securityContext:
+    runAsNonRoot: true
+    seccompProfile:
+      type: RuntimeDefault
   containers:
     - name: agent
       image: quay.io/konveyor/agentic-controller-agent:e2e
       command: ["sleep", "infinity"]
+      securityContext:
+        allowPrivilegeEscalation: false
+        capabilities:
+          drop: ["ALL"]
       volumeMounts:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
apiVersion: v1
kind: Pod
metadata:
name: imagevolume-probe
labels:
app.kubernetes.io/name: imagevolume-probe
app.kubernetes.io/component: diagnostic
spec:
restartPolicy: Never
# Override the stub entrypoint: we want a bare long-running process, not the
# stub's banner logic, so the probe measures the environment and nothing else.
containers:
- name: agent
image: quay.io/konveyor/agentic-controller-agent:e2e
command: ["sleep", "infinity"]
volumeMounts:
- name: skill
mountPath: /opt/skills/probe-skill
readOnly: true
- name: workspace
mountPath: /workspace
volumes:
# The mount under test. Any skill image works -- we are measuring how the
# kubelet/CRI mounts an ImageVolume, not the contents.
- name: skill
image:
reference: quay.io/konveyor/skills:maven-migration
# Matches the controller's workspace volume (agentrun_controller.go:332).
- name: workspace
emptyDir: {}
apiVersion: v1
kind: Pod
metadata:
name: imagevolume-probe
labels:
app.kubernetes.io/name: imagevolume-probe
app.kubernetes.io/component: diagnostic
spec:
restartPolicy: Never
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
# Override the stub entrypoint: we want a bare long-running process, not the
# stub's banner logic, so the probe measures the environment and nothing else.
containers:
- name: agent
image: quay.io/konveyor/agentic-controller-agent:e2e
command: ["sleep", "infinity"]
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: skill
mountPath: /opt/skills/probe-skill
readOnly: true
- name: workspace
mountPath: /workspace
volumes:
# The mount under test. Any skill image works -- we are measuring how the
# kubelet/CRI mounts an ImageVolume, not the contents.
- name: skill
image:
reference: quay.io/konveyor/skills:maven-migration
# Matches the controller's workspace volume (agentrun_controller.go:332).
- name: workspace
emptyDir: {}
🧰 Tools
🪛 Checkov (3.3.8)

[medium] 16-45: Containers should not run with allowPrivilegeEscalation

(CKV_K8S_20)


[medium] 16-45: Minimize the admission of root containers

(CKV_K8S_23)

🤖 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 `@hack/probe/probe-pod.yaml` around lines 16 - 45, Add a pod-level
securityContext to the imagevolume-probe Pod spec that satisfies Kubernetes
restricted Pod Security requirements, including non-root execution, seccomp
RuntimeDefault, and dropping all Linux capabilities. Keep the existing container
command, mounts, and volumes unchanged.

Source: Linters/SAST tools

Comment thread README.md
Comment on lines +59 to +65
- Kubernetes 1.33+ (ImageVolume beta, enabled by default)
- OpenShift 4.20+
- Agent Sandbox v0.5.x
- A container runtime that implements ImageVolume: CRI-O 1.31+ or containerd
2.0+. cri-dockerd does not implement it.

For a local cluster to develop against, see [docs/local-development.md](docs/local-development.md).

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files =="
git ls-files | rg '(^README\.md$|hack/probe/run-probe\.sh$|docs/local-development\.md$)' || true

echo "== README relevant lines =="
if [ -f README.md ]; then
  nl -ba README.md | sed -n '50,70p'
fi

echo "== run-probe relevant lines =="
if [ -f hack/probe/run-probe.sh ]; then
  nl -ba hack/probe/run-probe.sh | sed -n '90,115p'
fi

echo "== broader ImageVolume mentions =="
rg -n "ImageVolume|containerd 2\.|containerd >= 2\.0|needs >= 2\.0|CRI-O|Kubernetes 1\.33" README.md hack/probe/run-probe.sh docs 2>/dev/null || true

Repository: konveyor/agentic-controller

Length of output: 284


🌐 Web query:

Kubernetes v1.33 beta announcement containerd ImageVolume v2.1.0

💡 Result:

In Kubernetes v1.33, released in April 2025, the ImageVolume feature graduated to beta [1][2]. This feature, introduced via KEP-4639, enables pods to mount OCI images or artifacts directly as volumes [1][3]. Regarding containerd, support for the ImageVolume feature was implemented as part of the containerd v2.1.0 release [4][1]. Specifically: - Alpha Support: Initial support for the alpha version of the ImageVolume feature was included in containerd v2.1.0 [1]. - Beta Graduation & SubPath Support: While Kubernetes v1.33 brought the ImageVolume feature to beta (enabling subPath and subPathExpr support), containerd's support for these advanced beta requirements (such as subPath support) was integrated in later developments following the initial v2.1.0 release [5][1][2]. Because the feature remains disabled by default in Kubernetes due to the requirement for full runtime support, users must explicitly enable the ImageVolume feature gate on both the API server and the kubelet [1][6]. As of the v1.33 release, users were advised to ensure their container runtime environment was updated to a version with appropriate support [1].

Citations:


🌐 Web query:

kubernetes 1.33 image volumes beta enabled by default Kubernetes v1.33

💡 Result:

In Kubernetes v1.33, the Image Volumes feature graduated to beta, but it is not enabled by default [1][2][3]. To use Image Volumes in Kubernetes v1.33, you must manually enable the ImageVolume feature gate on your API server and kubelet [4][3]. The feature remains disabled by default primarily because not all container runtimes have full support for it [1][5]. While in beta, the feature introduced support for subPath and subPathExpr in volumeMounts, allowing users to mount specific subdirectories of an image volume [1][3][5]. Additionally, new kubelet metrics were added to track usage and monitor mount success or failure [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== README relevant lines =="
if [ -f README.md ]; then
  sed -n '50,70p' README.md
fi

echo "== run-probe relevant lines =="
if [ -f hack/probe/run-probe.sh ]; then
  sed -n '90,115p' hack/probe/run-probe.sh
fi

echo "== broader ImageVolume mentions =="
rg -n "ImageVolume|containerd 2\.|containerd >= 2\.0|needs >= 2\.0|CRI-O|Kubernetes 1\.33" README.md hack/probe/run-probe.sh docs 2>/dev/null || true

Repository: konveyor/agentic-controller

Length of output: 5634


Correct the Kubernetes and containerd prerequisites for ImageVolume.

ImageVolume is beta in Kubernetes 1.33 but is not enabled by default, and containerd support is at 2.1, so both README.md#L59-L65 and the containerd warning in hack/probe/run-probe.sh#L104 are now incorrect. Update the README default-enablement wording and bump both “containerd 2.0+” references to “containerd 2.1+”.

📍 Affects 2 files
  • README.md#L59-L65 (this comment)
  • hack/probe/run-probe.sh#L103-L105
🤖 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 `@README.md` around lines 59 - 65, Update the ImageVolume prerequisites in
README.md at lines 59-65 to state that Kubernetes 1.33+ has ImageVolume beta
enabled but not by default, and change the containerd requirement from 2.0+ to
2.1+. Update the containerd warning in hack/probe/run-probe.sh at lines 103-105
from 2.0+ to 2.1+; no other prerequisite changes are needed.

@fabianvf

Copy link
Copy Markdown
Contributor Author

Closing — wrong scope. This shipped the investigation apparatus rather than the change that actually fixes anything. The fix is a ~20-line prompt addition in the harness, which needs to land on top of #53. Will open that once #53 merges.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant