Skip to content

The surveyor cannot resolve the default-branch CI classifier's admitted path, so rung 0 is blind on every repository #200

Description

@devantler

🤖 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

  • The surveyor has a documented, runtime-independent way to reach the admitted classifier — the
    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.
  • A survey run reports a real per-repository default-branch verdict (green / named red
    identities) rather than unknown, on a checkout where the classifier is installed.
  • The guard is unchanged in strength: offline --input stays denied, and only the remote-mode
    argument shape is admitted.
  • Failure still fails closed — an unresolvable classifier reports unknown, never green.

Rough size: small-to-medium. The mechanism is understood and the fix is a resolution step rather
than new policy.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions