What was found
grep is not on tend's allow-list, so any generated dev-loop skill that
prescribes a grep command has prescribed a step a headless dispatch
cannot run. This is a template-level property rather than a
one-repo problem, because every generated skill is dispatched through the
same TEND_BASE_ALLOWED_TOOLS.
Evidence
TEND_BASE_ALLOWED_TOOLS (gardener/dispatch.py:479-532 in
Stephenson-Software/gardener) was read in full. Its Bash patterns are
Bash(git *), an enumerated set of gh subcommands, and a set of
build/test verbs (python3, python, pip, npm, pytest, go, mvn,
gradle, make, cargo). There is no Bash(grep *) entry.
Bash(ls *) is a near miss worth naming so it is not mistaken for a
counter-example: it appears at gardener/dispatch.py:602, inside
MODE_SPECS[Mode.CREATE_DEV_LOOP] only, not in the tend base list.
The denial was observed live, not merely inferred. During a gardener tend
dispatch of Stephenson-Software/gardener-dev-loop on 2026-08-18,
grep -n '^```bash' gardener-dev-loop.md was rejected with "This command
requires approval", while git clone, git log and git grep in the same
session ran without a prompt.
Why it matters at the template level
Two consequences were seen in the same session:
- The generated skill's mandated validation checklist was written as
grep -n / grep -c invocations. A checklist that cannot be executed
cannot be the merge anchor it is designated as, and a dispatch that
reports it green has reported something it did not run.
- A verify-by-removal assertion was written as
grep -c '<token>' <path>, with the surrounding text identifying that
assertion as the thing separating a real removal from a silent no-op.
The denied step was therefore precisely the load-bearing one.
Substitutes that do work
- The
Grep tool rather than the grep command. tend_mode_spec()
grants tools=("Read", "Grep", "Glob", "Edit", "Write", "Bash", "Skill")
(gardener/dispatch.py:627), so the capability is present; only the
shell form is out of reach. Note the pass condition changes shape: the
tool reports matching lines rather than a count, so "no output" replaces
"prints 0" — which incidentally removes the grep -c-exits-1-on-zero
trap.
git grep -n <pattern> <rev> -- <path> for searching a revision
rather than the working tree. It is inside Bash(git *) and was executed
successfully in the same dispatch. This is the form a Verification-fix
step needs when it must quote what the base ref said.
Suggested resolution
Two parts, both in the template rather than in any one generated skill:
- Stop emitting
grep/ls Bash commands into generated skills —
particularly into the validation checklist and any verify-by-removal
block — and emit the Grep tool or git grep instead.
- Add a
grep/ls row to the allowed-tools substitution table the
template already generates, alongside its existing PYTHONPATH=,
gh api/gh pr review, gh label list and rm rows, so a cycle that
hits the denial has something to look up.
Related
The concrete instance of this was fixed in
Stephenson-Software/gardener-dev-loop (issue #36, PR #37) rather than
being left to diverge silently. That fix is deliberately scoped to the two
commands that file prescribes; the general rule is raised here.
This issue body was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).
drafted by Claude on behalf of Daniel Stephenson
What was found
grepis not ontend's allow-list, so any generated dev-loop skill thatprescribes a
grepcommand has prescribed a step a headless dispatchcannot run. This is a template-level property rather than a
one-repo problem, because every generated skill is dispatched through the
same
TEND_BASE_ALLOWED_TOOLS.Evidence
TEND_BASE_ALLOWED_TOOLS(gardener/dispatch.py:479-532inStephenson-Software/gardener) was read in full. Its Bash patterns areBash(git *), an enumerated set ofghsubcommands, and a set ofbuild/test verbs (
python3,python,pip,npm,pytest,go,mvn,gradle,make,cargo). There is noBash(grep *)entry.Bash(ls *)is a near miss worth naming so it is not mistaken for acounter-example: it appears at
gardener/dispatch.py:602, insideMODE_SPECS[Mode.CREATE_DEV_LOOP]only, not in the tend base list.The denial was observed live, not merely inferred. During a
gardener tenddispatch of
Stephenson-Software/gardener-dev-loopon 2026-08-18,grep -n '^```bash' gardener-dev-loop.mdwas rejected with "This commandrequires approval", while
git clone,git logandgit grepin the samesession ran without a prompt.
Why it matters at the template level
Two consequences were seen in the same session:
grep -n/grep -cinvocations. A checklist that cannot be executedcannot be the merge anchor it is designated as, and a dispatch that
reports it green has reported something it did not run.
grep -c '<token>' <path>, with the surrounding text identifying thatassertion as the thing separating a real removal from a silent no-op.
The denied step was therefore precisely the load-bearing one.
Substitutes that do work
Greptool rather than thegrepcommand.tend_mode_spec()grants
tools=("Read", "Grep", "Glob", "Edit", "Write", "Bash", "Skill")(
gardener/dispatch.py:627), so the capability is present; only theshell form is out of reach. Note the pass condition changes shape: the
tool reports matching lines rather than a count, so "no output" replaces
"prints
0" — which incidentally removes thegrep -c-exits-1-on-zerotrap.
git grep -n <pattern> <rev> -- <path>for searching a revisionrather than the working tree. It is inside
Bash(git *)and was executedsuccessfully in the same dispatch. This is the form a Verification-fix
step needs when it must quote what the base ref said.
Suggested resolution
Two parts, both in the template rather than in any one generated skill:
grep/lsBash commands into generated skills —particularly into the validation checklist and any verify-by-removal
block — and emit the
Greptool orgit grepinstead.grep/lsrow to the allowed-tools substitution table thetemplate already generates, alongside its existing
PYTHONPATH=,gh api/gh pr review,gh label listandrmrows, so a cycle thathits the denial has something to look up.
Related
The concrete instance of this was fixed in
Stephenson-Software/gardener-dev-loop(issue #36, PR #37) rather thanbeing left to diverge silently. That fix is deliberately scoped to the two
commands that file prescribes; the general rule is raised here.
This issue body was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).
drafted by Claude on behalf of Daniel Stephenson