Skip to content

grep is not on tend's allow-list, so generated validation checklists and verify-by-removal assertions cannot run headlessly #127

Description

@dmccoystephenson

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:

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

  1. 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.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    template-ruleShould be promoted into create-dev-loop.md

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions