What happened
A task about a web server is refused before it starts when its deliverable or acceptance names an HTTP route. The ground lint reads a route like /top or /links as an absolute folder outside the task's ground:
this task names a folder it does not stand in: /top
The model usually rewrites the contract and proposes again, so the conversation fills with refused propose_task calls ("stuck: propose_task has failed the same way 3 times") and the work starts late or not at all. Any web project hits it, because routes are the natural way to say what an HTTP task delivers.
Seen on dev@7cb6fd58f, codeaf chat in any git repository.
Replication
Deterministic (no model). Add this beside TestATaskNamingAFolderItDoesNotStandInIsRefused in internal/session/taskstands_test.go:
func TestARouteInTheContractIsNotAFolder(t *testing.T) {
repo := newTestRepo(t)
agent, _ := newTestAgent(t, &scriptedCompleter{}, func(config *Config) {
config.Workspace = repo
})
arguments, _ := json.Marshal(taskArguments{
Title: "top links page", Summary: "s", Brief: "Add a page listing the most clicked links.",
Deliverable: "a GET /top handler and its test",
Acceptance: "GET /top returns 200 and lists links by clicks; POST /links still returns 201",
})
result, isError, err := agent.proposeTask(context.Background(), arguments)
if err != nil {
t.Fatalf("proposeTask errored the turn: %v", err)
}
if isError {
t.Fatalf("refused: %q", result)
}
}
make test-focus PKGS=./internal/session RUN='^TestARouteInTheContractIsNotAFolder$$'
Today it fails:
route_repro_test.go:24: refused: "this task names a folder it does not stand in: /top"
Field (real models). In a small Go HTTP service in a git repository, run codeaf chat with any open model (e.g. --model deepseek/deepseek-v4.1-flash) and ask for two or three HTTP features as separate tasks, for example "rate limit link creation per IP and add a page listing the most clicked links, one task each". Most runs show at least one refusal naming a route. A few cents, a couple of minutes.
Where
groundLint in internal/session/taskstands.go: the second loop over pathTokens(spec.deliverable + "\n" + spec.acceptance) refuses any absolute token that groundHolds rejects. pathTokens / looksLikePath treat every token containing / as a path, so /top, /links, /{code} and /api/v1/users all qualify.
The fix
A token that looks absolute but whose first folder does not exist on this machine names a route, not a place, and is not a reason to refuse. Real absolute paths outside the ground (/Users/..., /home/..., /tmp/...) keep refusing exactly as today. Checking the first component on disk is enough and costs one stat per token; a route that happens to share a top-level name with a real root folder (/usr, /opt) still refuses, which is acceptable and rare.
Acceptance
- e2e: the tmux suite drives a proposal whose acceptance says
GET /top returns 200 in a git repository and asserts the task row appears running, with no names a folder it does not stand in line on screen.
- e2e: the control: a proposal whose deliverable names a real directory outside the ground is still refused with
this task names a folder it does not stand in: followed by that directory.
- Unit:
TestARouteInTheContractIsNotAFolder above passes, and TestATaskNamingAFolderItDoesNotStandInIsRefused still passes.
- The manual page on where a task works (
internal/manual/chat/) says a route in a task's contract is not read as a folder, and the change entry's invalidates names the old refusal.
Drafted with CodeAF · reviewed and owned by the author
What happened
A task about a web server is refused before it starts when its deliverable or acceptance names an HTTP route. The ground lint reads a route like
/topor/linksas an absolute folder outside the task's ground:The model usually rewrites the contract and proposes again, so the conversation fills with refused
propose_taskcalls ("stuck: propose_task has failed the same way 3 times") and the work starts late or not at all. Any web project hits it, because routes are the natural way to say what an HTTP task delivers.Seen on
dev@7cb6fd58f,codeaf chatin any git repository.Replication
Deterministic (no model). Add this beside
TestATaskNamingAFolderItDoesNotStandInIsRefusedininternal/session/taskstands_test.go:Today it fails:
Field (real models). In a small Go HTTP service in a git repository, run
codeaf chatwith any open model (e.g.--model deepseek/deepseek-v4.1-flash) and ask for two or three HTTP features as separate tasks, for example "rate limit link creation per IP and add a page listing the most clicked links, one task each". Most runs show at least one refusal naming a route. A few cents, a couple of minutes.Where
groundLintininternal/session/taskstands.go: the second loop overpathTokens(spec.deliverable + "\n" + spec.acceptance)refuses any absolute token thatgroundHoldsrejects.pathTokens/looksLikePathtreat every token containing/as a path, so/top,/links,/{code}and/api/v1/usersall qualify.The fix
A token that looks absolute but whose first folder does not exist on this machine names a route, not a place, and is not a reason to refuse. Real absolute paths outside the ground (
/Users/...,/home/...,/tmp/...) keep refusing exactly as today. Checking the first component on disk is enough and costs one stat per token; a route that happens to share a top-level name with a real root folder (/usr,/opt) still refuses, which is acceptable and rare.Acceptance
GET /top returns 200in a git repository and asserts the task row appears running, with nonames a folder it does not stand inline on screen.this task names a folder it does not stand in:followed by that directory.TestARouteInTheContractIsNotAFolderabove passes, andTestATaskNamingAFolderItDoesNotStandInIsRefusedstill passes.internal/manual/chat/) says a route in a task's contract is not read as a folder, and the change entry'sinvalidatesnames the old refusal.Drafted with CodeAF · reviewed and owned by the author