Skip to content

fix(engine): respect ctx cancel on stream retry, surface self-review revert failure#32

Merged
Patel230 merged 5 commits into
mainfrom
fix/agent-loop-safety-and-tool-classification
Jun 12, 2026
Merged

fix(engine): respect ctx cancel on stream retry, surface self-review revert failure#32
Patel230 merged 5 commits into
mainfrom
fix/agent-loop-safety-and-tool-classification

Conversation

@Patel230

Copy link
Copy Markdown
Contributor

Three related fixes in the hawk agent loop.

1. Stream retry no longer blocks ctx cancellation

hawk/internal/engine/stream.go:479

Previously: time.Sleep(time.Duration(streamAttempt+1) * time.Second) — a user-initiated cancel during a reasoning-only retry's backoff was ignored for up to 3s.

Now uses a select with ctx.Done() so cancel is observed immediately, the in-flight stream is closed via result.Close(), and the loop exits with a structured error event.

2. Self-review-before-apply now returns a hard error when revert fails

hawk/internal/engine/stream_tool_exec.go:208-230

Previously: if the LLM said 'this diff is bad' and we tried to revert (os.Remove or os.WriteFile) and that syscall failed, the code only logged s.log.Warn and proceeded with the rejected diff still on disk.

Now: capture the revert error, log it at Error level, and surface a hard tool error ("Self-review rejected the change AND the revert failed: ${err}. Manual intervention required.") so the LLM can't continue building on top of code it just flagged as broken.

3. safeConcurrent allowlist consolidated into tool.ReadOnlyTools

hawk/internal/tool/tool.go and the two call sites.

Previously: stream.go:716 and stream_tool_exec.go:29 both hardcoded the same map {Read, Grep, Glob, LS, WebSearch, WebFetch, ToolSearch} — drift waiting to happen.

Now: internal/tool/tool.go exports ReadOnlyTools (the set) and IsReadOnly(name) (canonicalising lookup). Both call sites go through it. A new TestIsReadOnly + TestReadOnlyToolsSetContainsExpectedNames test locks the contract so a future removal of e.g. Read from the allowlist fails CI in internal/tool/ rather than silently breaking concurrency classification in internal/engine/.

Verification

  • go build ./...
  • go test ./internal/tool/ -count=1
  • go test ./internal/engine/ -count=1 -short
  • gofumpt -l internal/tool/ internal/engine/ clean

Patel230 added 4 commits June 12, 2026 17:57
ai_passage.md was a 53-line, ~1000-word essay on the history and
ethics of AI in general — entirely unrelated to the hawk project,
no README/AGENTS.md/CHANGELOG.md reference to it. It looks like
LLM-generated filler committed in '99261ca Fix CI formatting and
toolchain hygiene' to satisfy a 'must have an essay' requirement
that no longer applies. Untrack and delete.
…revert failure

Three related fixes in the hawk agent loop:

1. **stream.go Stream retry no longer blocks ctx cancellation.**
   Previously: `time.Sleep(time.Duration(streamAttempt+1) * time.Second)` — a
   user-initiated cancel during a reasoning-only retry's backoff was ignored for
   up to 3s. Now uses a select with ctx.Done() so cancel is observed immediately,
   the in-flight stream is closed, and the loop exits with a structured error event.

2. **stream_tool_exec.go Self-review-before-apply now returns a hard error
   when the revert itself fails.** Previously: if the LLM said 'this diff is bad'
   and we tried to revert (os.Remove or os.WriteFile) and that syscall failed, the
   code only logged s.log.Warn and proceeded with the rejected diff still on disk.
   Now: capture the revert error, log it at Error level, and surface a hard tool
   error ('Self-review rejected the change AND the revert failed: <err>. Manual
   intervention required.') so the LLM can't continue building on top of code it
   just flagged as broken.

3. **internal/tool/tool.go now exports ReadOnlyTools + IsReadOnly() so the
   safe-concurrent allowlist has a single source of truth.** The map was previously
   duplicated in stream.go:716 and stream_tool_exec.go:29 with identical content
   — drift waiting to happen. Both call sites now go through tool.IsReadOnly(name),
   which canonicalises aliases (read/file_read, ls, etc.) before lookup. A new
   test (TestIsReadOnly, TestReadOnlyToolsSetContainsExpectedNames) locks the
   contract so a future removal of e.g. 'Read' from the allowlist fails CI in
   internal/tool/ rather than silently breaking concurrency classification in
   internal/engine/.
@Patel230 Patel230 enabled auto-merge (squash) June 12, 2026 20:19
@Patel230 Patel230 merged commit 68377ff into main Jun 12, 2026
17 checks passed
@Patel230 Patel230 deleted the fix/agent-loop-safety-and-tool-classification branch June 12, 2026 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant