Skip to content

[bug] Chat tells the agent a repo is "needs_attention" and drops the sentence saying why — #405's diagnosis never reaches the model #416

Description

@serge-ivo

#405 (5527cea) exists because an agent, asked about code at a workdir that was empty and not a
checkout, was handed two non-answers describing no problem — and invented the repository. The fix
wrote a real diagnosis: one sentence, deliberately phrased "to be RELAYED: an agent can say it to
the owner verbatim and it is both true and actionable."

The chat context does not carry it. The model is told needs_attention and nothing else.

Honest — far better than the old false ready — but a bare enum token is not something an agent
can say out loud, and relaying the diagnosis was the point of the ticket.

Where

workers/api/src/agent-think.ts:493-502:

conststatus=r.cloneStatus==="ready"
? "ready"
: r.cloneStatus==="cloning"
? "cloning…"
: r.cloneStatus==="error"
? `clone error${r.cloneError ? `: ${r.cloneError}` : ""}`
: r.cloneStatus;systemPrompt+=`- ${r.name}${r.githubRepo ? ` (${r.githubRepo})` : ""}${status}\n`;

CloneStatus has six values (lib/coding-types.ts:20):
unknown | cloning | ready | missing_url | error | needs_attention. The chain handles three. The
cloneError field — right there on the same object (:48) — is read on exactly one branch.

So the prompt says:

- apps/chess-academy — needs_attention

when the platform already stored, in clone_error:

The configured checkout /Users/…/apps/chess-academy exists but is EMPTY — nothing was ever
cloned into it, or its contents were moved away. There is no code at that path to read.

Mechanism

Two correct things, unjoined. verifyLocalWorkdir (routes/coding-repos.ts:104-119) persists both
halves together — updateRepoClone(env, repo.id, { cloneStatus: status, cloneError }) — and
verdictFromCheck (lib/coding-workdir.ts:73-113) writes a distinct sentence for each of
missing / not_a_directory / empty / not_a_git_repo / unverified. The prompt builder is a
ternary chain that predates the new statuses, so anything it does not enumerate falls through as
the raw token. Nothing warns: adding a CloneStatus value is type-safe against a fallthrough that
prints it.

unknown falls through the same way, and it is the other state #405 created — "no machine
connected, nobody has looked at this path"
— which is a genuinely important thing for an agent to
be able to say instead of guessing.

Do — cheapest first

  • Render cloneError for every non-ready status, not just error. One expression:
    append `: ${r.cloneError}` whenever it is present.
  • Replace the ternary chain with a phrase table typed
    satisfies Record<CloneStatus, string>, so the next new status is a compile error rather
    than a token leaking into a prompt. That is the actual defence; the line above is the fix.
  • Give needs_attention and unknown prose the model can relay — e.g.
    "UNUSABLE — <cloneError>" and "not checked (no machine connected)" — rather than the enum
    name dressed up.

A separate decision, worth stating rather than drifting into

agent-think.ts:392 reads repos through listRepos (lib/coding-store.ts:104-111), a plain
SELECT. So the chat shows the last persisted verdict, refreshed only when the console's repos
route or add-repo last ran verifyLocalWorkdir. That is probably right — a relay round-trip per
chat turn is not worth it, and the repos list re-checks on every read (capped at
MAX_VERIFY_PER_LIST = 12). But it is currently an accident rather than a decision. If it stays,
consider saying when the verdict was taken, so an agent can distinguish "it is broken" from "it
was broken when someone last looked."

Alternatives considered

  • Have the prompt builder run the workdir check itself. Rejected: a relay round-trip on every
    chat turn, on the latency path, for a fact that changes rarely and is already refreshed by the
    surface the user is looking at.
  • Put the sentence in memory so the agent "knows" it. Rejected outright — this is live platform
    state, and memory holding a stale copy of it is precisely the phantom-repository failure that
    repo-chat removed its mem:repository entry to escape.

Acceptance

  • A unit test on the prompt builder: a repo with cloneStatus: "needs_attention" and a
    cloneError produces a line containing that sentence.
  • Adding a value to CloneStatus without giving it a phrase fails typecheck.
  • Asked about such a repo, the agent's reply names the path and the condition (visible in
    agent_trace) instead of the word needs_attention.

Regression risk

  • Prompt length. Each sentence is ~120 characters, one per broken repo, bounded by the number
    of attached repos. Small, but if an instance has many broken repos it is now many sentences —
    consider capping at the first N and summarising the rest.
  • Saying it twice.agent-think.ts:398 also calls selfDescriptionPrompt(selfModel, { repoSetting, attached })
    (lib/agent-self-description.ts:250-300), which renders attached repos for its own purpose. Check
    it before adding the sentence in a second place; the console and the chat describing one directory
    two different ways is the failure #405 explicitly designed against.

Files: workers/api/src/agent-think.ts:392,398,492-502, workers/api/src/lib/coding-types.ts:20,47-48,
workers/api/src/lib/coding-workdir.ts:43-52,73-131, workers/api/src/routes/coding-repos.ts:104-119,
workers/api/src/lib/coding-store.ts:104-111, workers/api/src/lib/agent-self-description.ts:250-300.
Related: #405 (which produced the diagnosis), #395 (the fabrication it removes the reason
for).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend / Worker / API workbugSomething isn't workingcopilotCo-pilot (chat assistant) behavior

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions