🤖 Generated by the Agentic Engineer
Evidence
The surveyor's rung-0 step tells it to run the shipped default-branch classifier and forbids
reimplementing it:
Then invoke the shipped ../scripts/classify-default-branch-ci-runs.sh
with the repository, default-branch name, and that exact sha, resolving it from the installed,
reviewed plugin path. Do not reimplement the helper inline.
— agents/portfolio-surveyor.agent.md §4, at 4.4.22
The guard admits exactly one spelling of that call. forge-readonly-guard.sh sets
GUARD_DIR from its own location and matches only ${GUARD_DIR}/classify-default-branch-ci-runs.sh,
with the comment "Match the exact sibling of this guard, never a basename or caller-supplied path".
The consuming hook invokes the adapter with SURVEYOR_FORGE_READONLY_GUARD="${install_path}/scripts/forge-readonly-guard.sh",
so the only admitted path is ${install_path}/scripts/classify-default-branch-ci-runs.sh, where
install_path comes from the runtime registry (~/.claude/plugins/installed_plugins.json).
Nothing tells the surveyor what install_path is. The single pointer it has is a relative
markdown link, which resolves against the agent file inside the plugin — a location the surveyor
does not know as an absolute path. The runtime install root is version-numbered
(.../agentic-engineering/4.4.22) and changes on every plugin update, so it cannot be learned once
either.
Measured on a survey run 2026-09-06: every path the surveyor could construct was denied
a read must begin with a forge command — the consumer's .claude/scripts/, the marketplace
checkout, the plugin cache, the repos directory, and the libraries/agent-plugins submodule. The
one path that would have been admitted is the registry's installPath, which the surveyor never
derives. All three siblings do exist together under that root, so the guard's sibling rule is
satisfied there; the gap is purely that the caller cannot name it.
Impact
Rung 0 of the work-selection ladder is blind. The survey reported default-branch CI health as
unknown for all 19 active repositories, and nothing_on_fire: false follows from the unknown
rather than from any observed failure. Since a run works the ladder top-down, live breakage on a
default branch is not detected by the survey at all; it is found only if some other step happens to
trip over it.
This fails closed, which is the safe direction, but the cost is not zero: the top rung produces no
signal on any run, and the instruction's own prohibition on reimplementing the helper means the
surveyor cannot substitute anything.
This is the same class as monorepo#3123 (the programmed-bot exemption classifier is unreachable
under the same guard) but a different script, a different admission path, and a wider blast radius:
#3123 costs one PR classification per release, this costs rung-0 coverage on every repository on
every run.
Expected behaviour
The surveyor can invoke the mandated classifier at a path the guard admits, without relaxing the
guard and without reimplementing the helper.
Acceptance criteria
Rough size: small-to-medium. The mechanism is understood and the fix is a resolution step rather
than new policy.
Evidence
The surveyor's rung-0 step tells it to run the shipped default-branch classifier and forbids
reimplementing it:
The guard admits exactly one spelling of that call.
forge-readonly-guard.shsetsGUARD_DIRfrom its own location and matches only${GUARD_DIR}/classify-default-branch-ci-runs.sh,with the comment "Match the exact sibling of this guard, never a basename or caller-supplied path".
The consuming hook invokes the adapter with
SURVEYOR_FORGE_READONLY_GUARD="${install_path}/scripts/forge-readonly-guard.sh",so the only admitted path is
${install_path}/scripts/classify-default-branch-ci-runs.sh, whereinstall_pathcomes from the runtime registry (~/.claude/plugins/installed_plugins.json).Nothing tells the surveyor what
install_pathis. The single pointer it has is a relativemarkdown link, which resolves against the agent file inside the plugin — a location the surveyor
does not know as an absolute path. The runtime install root is version-numbered
(
.../agentic-engineering/4.4.22) and changes on every plugin update, so it cannot be learned onceeither.
Measured on a survey run 2026-09-06: every path the surveyor could construct was denied
a read must begin with a forge command— the consumer's.claude/scripts/, the marketplacecheckout, the plugin cache, the repos directory, and the
libraries/agent-pluginssubmodule. Theone path that would have been admitted is the registry's
installPath, which the surveyor neverderives. All three siblings do exist together under that root, so the guard's sibling rule is
satisfied there; the gap is purely that the caller cannot name it.
Impact
Rung 0 of the work-selection ladder is blind. The survey reported default-branch CI health as
unknownfor all 19 active repositories, andnothing_on_fire: falsefollows from the unknownrather than from any observed failure. Since a run works the ladder top-down, live breakage on a
default branch is not detected by the survey at all; it is found only if some other step happens to
trip over it.
This fails closed, which is the safe direction, but the cost is not zero: the top rung produces no
signal on any run, and the instruction's own prohibition on reimplementing the helper means the
surveyor cannot substitute anything.
This is the same class as monorepo#3123 (the programmed-bot exemption classifier is unreachable
under the same guard) but a different script, a different admission path, and a wider blast radius:
#3123 costs one PR classification per release, this costs rung-0 coverage on every repository on
every run.
Expected behaviour
The surveyor can invoke the mandated classifier at a path the guard admits, without relaxing the
guard and without reimplementing the helper.
Acceptance criteria
resolution step spelled out where the hook already performs it, an environment variable the
adapter exports, or a wrapper the guard admits by shape. The plugin version in the path must
not have to be known in advance.
green/ named redidentities) rather than
unknown, on a checkout where the classifier is installed.--inputstays denied, and only the remote-modeargument shape is admitted.
unknown, never green.Rough size: small-to-medium. The mechanism is understood and the fix is a resolution step rather
than new policy.