After my third PR where the agent had clearly ignored a comment thread that pivoted the spec, I stopped blaming the prompt.
The first post in this series argued that the prompt is the request and the feedback loop is the configuration. That framing held up. But it left out a whole category of input I kept underestimating: the cloud agent does not just read your prompt. It reads your issue structure, your git hooks, the hook decisions you wire up at the end of a turn, and the environment variables you assume are there but aren’t. Most of that is never announced. Some of it helps. Some of it quietly breaks your work.
The prompt is what you say. The feedback loop is what you enforce. The implicit signals are what the agent picks up that you didn’t think you were saying.
This is a running list of four of those signals — what each one does, and how to either weaponize or defuse it.
Finding 1 — The issue description silently overrides the comments
When a GitHub issue has a description and a comment thread that later refines, changes, or reverses the spec, the cloud agent anchors on the description. The comments register. They just don’t win.
I saw this repeatedly on issues where the original ask got corrected in discussion — a “don’t do X, do Y instead” reply, an agreed-upon pivot, a resolved ambiguity. The resulting PR matched the original description almost every time. Not randomly wrong — traceably wrong, back to the first paragraph of the issue.
None of this is Copilot-specific, once I traced it. The description is the first long block of text in context, and models anchor on what they see first. The description also reads as “the spec” while comments read as “the discussion” — a framing that predates AI entirely, it’s just how issue trackers are shaped, but becomes load-bearing the moment an LLM has to arbitrate a conflict between the two. And nothing in the prompt tells the agent how to resolve that conflict, so the implicit hierarchy from training data fills the gap.
I am not against comment threads doing the work of refining a spec. I am against expecting the agent to reliably notice when they have.
Mitigations, cheapest to most reliable:
- Add a precedence rule to your instructions file: when description and comments disagree, treat the most recent explicit requirement in the comments as canonical, and reconcile before planning.
- Reserve a magic-marker comment shape —
**Current spec:**or a[SPEC]tag — and teach the agent to look for it. - Edit the description when the spec changes. Don’t append a correction as a comment. The description is the canonical surface; treat it like one.
The instruction-file rule is the cheap fix. Editing the description is the reliable one. Do both — the rule catches the times someone forgets to update the description, and the discipline catches the times the rule wasn’t explicit enough. I’d expect the same anchoring on the agent’s own PR — a review comment that shifts direction competing with the PR description the same way a new issue comment competes with the original ask — though I haven’t caught it in the act there yet the way I have on issues.
Finding 2 — Git hooks gate the agent the same way they gate you
copilot-setup-steps.yml is the workflow file GitHub runs to provision the agent’s environment before it starts working — installing dependencies, warming caches, whatever your repo needs. If your repo has Husky, lefthook, pre-commit, or any other checked-in hook manager, and that setup workflow actually triggers its install step, those hooks activate inside the agent’s environment. From that point on, every git operation the agent attempts is gated by the same checks a human developer hits.
That’s a free feedback loop most teams already have and don’t realize they have. If your pre-commit runs lint-staged, a type check, and a fast test pass, the agent is already walking through that gate before any commit lands — no extra configuration required.
The gotcha is the install pathway. Hook managers only activate if their install step actually runs — prepare in package.json for Husky, an explicit lefthook install, pre-commit install for the Python equivalent. npm ci --ignore-scripts is a common CI hardening step, and it silently skips all of that. The hooks never install. The agent commits straight past lint and tests, and nothing in the output tells you why.
I’ve since started treating this as a general rule, not just a hook-manager quirk: the agent’s environment inherits every side effect of your install scripts, not just the dependencies they pull. Generated config, auto-installed git filters, anything a postinstall does — happens in the agent’s environment too, the same as it would in a CI image. Worth auditing that surface with the same care you’d give a CI image, even though I’ve only confirmed it directly for hook installs so far.
Where this layers with Copilot’s own agent hooks (more on those next): use git hooks for universal quality gates that should apply to every commit regardless of who’s making it. Use Copilot’s preToolUse/postToolUse hooks for agent-specific guardrails — path restrictions, destructive-command checks — that have no equivalent for a human developer. They stack cleanly because they gate at different granularities: git hooks per git command, Copilot hooks per tool call.
Finding 3 — agentStop isn’t a notification, it’s a decision
The parent post covered preToolUse and postToolUse — hooks that gate or observe individual tool calls. There’s a second pair of hook events that fire at the edges of a whole turn instead of a single tool call: agentStop, when the main agent thinks it’s done responding, and subagentStop, when a sub-agent finishes before handing results back up.
The part I underestimated: an agentStop hook that returns {"decision": "block", "reason": "..."} doesn’t just log a failure. Per the hooks reference, a block decision forces another agent turn, using reason as the prompt for it — the agent keeps working. That’s the actual mechanical answer to the parent post’s core problem — a cloud agent that exits with false confidence because nothing challenged it. agentStop is the challenge.
Here’s the shape I run in one of my own repos — a full CI gate on agentStop, and a cheaper check on subagentStop for the inner loop:
#!/bin/bash # Full CI gate for agentStop. # Outputs {"decision":"block","reason":"..."} on failure so the agent # receives the error output as context and continues working to fix it. # Always exits 0 — a non-zero exit signals a hook infrastructure error, # not a block; the decision field controls the gate. # Requires: jq set -uo pipefail output=$( (task fmt && \ task lint && \ go build ./... && \ task test ./...) 2>&1) || { jq -n --arg r "CI gate failed. Fix all issues before finishing: $output" '{"decision":"block","reason":$r}' exit 0 } echo '{"decision":"allow"}'
The subagentStop version skips the build and test run — a sub-agent runs mid-loop, before build artifacts necessarily exist, and the developer agent already runs its own tests before declaring done. It only needs to catch the cheap stuff, fast:
#!/bin/bash # Quick CI gate for subagentStop. # Requires: jq set -uo pipefail output=$(task lint 2>&1) || { jq -n --arg r "Quick CI check failed (task lint). Fix before finishing: $output" '{"decision":"block","reason":$r}' exit 0 } echo '{"decision":"allow"}'
Both scripts always exit 0 — a non-zero exit signals a broken hook, not a failed check. That distinction lives entirely in the JSON decision field, and only "block" or "allow" are valid values here. Get the exit code backwards and you can’t tell “the agent’s code is broken” apart from “the hook itself crashed,” which is exactly the kind of silent failure this whole post is about.
Finding 4 — $GITHUB_ENV doesn’t reach the agent. A separate secrets surface does
Once I knew hooks could talk back to the agent, I wanted to make the git hooks from Finding 2 agent-aware too — run the full test suite on pre-push when the cloud agent is committing, but keep the fast path for local dev. The idea: set IS_RUNNING_IN_CLOUD_AGENT=true somewhere in copilot-setup-steps.yml, then branch on it inside the hook script.
The instinctive way to set that variable is echo "IS_RUNNING_IN_CLOUD_AGENT=true" >> "$GITHUB_ENV" in a setup step, or a job-level env: block on the copilot-setup-steps job. Neither one reaches the actual agent runtime — and multiple developers report job-level and root-level env: failing even earlier than that, inside the setup job’s own later steps, with only step-level env: working reliably there. Either way, nothing set via that path propagates to the session where the agent is actually working and where your git hooks actually fire.
That’s a sharper version of the gotcha from Finding 2, not a contradiction of it. The filesystem side effects of copilot-setup-steps.yml — an installed hook, a generated config file — persist, because the agent’s session runs on the same disk that setup workflow just provisioned. An environment variable set inside that one Actions job does not persist, because the agent’s session is a separate process that doesn’t inherit that job’s shell environment at all. Same setup file, two different propagation rules depending on what kind of side effect you’re relying on.
The mechanism that does reach the agent is a separate configuration surface: Copilot cloud agent has its own dedicated Agents secrets and variables, configured at the organization or repository level. Per the official docs:
Once configured, Agents secrets and variables are automatically available to Copilot cloud agent when it works on a task in the repository. They are exposed to the agent as environment variables in its development environment, so they can be used by scripts and tools that Copilot runs, including by your
copilot-setup-steps.ymlworkflow.
Agents secrets/variables are their own category, separate from Actions secrets, Codespaces secrets, and Dependabot secrets — the docs are explicit that Copilot cloud agent doesn’t get access to those other three at all, only to Agents secrets/variables. That’s easy to miss from the repo settings page, where all four categories sit next to each other looking interchangeable.
The actual fix, then: set IS_RUNNING_IN_CLOUD_AGENT as an Agents variable (not secret — it’s not sensitive) at the repository level. It shows up as a real environment variable both during copilot-setup-steps.yml and during the agent’s actual session, which means the same variable is visible to whatever hook script eventually reads it.
One exception worth knowing before you rely on this for anything else: any Agents secret or variable prefixed COPILOT_MCP_ skips the agent’s shell environment and goes to MCP servers only. Wiring a credential into an MCP server config is a different naming convention than wiring a value into a git hook, and mixing the two up fails the same way everything else in this post fails — quietly.
The thread so far
The parent post’s reframe was: the prompt is the request, the feedback loop is the configuration. This post adds a layer underneath both of those:
The agent does not fail loudly. It reads the shape of everything you gave it — the issue, the hooks, the environment — and if that shape says something you didn’t intend, it will act on the wrong thing with full confidence.
You cannot eliminate implicit signals. They’re how the agent reads context at all: position and structure decide whether a description or a comment wins, install-time side effects decide whether your hooks actually gate anything, a decision field decides whether the agent gets a second turn to fix itself, and a secrets surface you’ve never opened decides whether an environment variable exists at all. You can only get deliberate about which ones win, and check, deliberately, that the ones you think are wired up actually are.
Further reading
- About hooks for GitHub Copilot — the full event list, including
agentStopandsubagentStop. - GitHub Copilot hooks reference — payload shapes, the
decisionfield, and fail-open vs. fail-closed behavior per hook type. - Configure secrets and variables for Copilot cloud agent — the Agents secrets/variables surface, and the
COPILOT_MCP_prefix convention. - Customizing the development environment — how
copilot-setup-steps.ymlrelates to (and differs from) the agent’s actual runtime. - GitHub Cloud Agents Work When You Build the Feedback Loop — the parent post this one extends.