Skip to content

Step in when a request can't make progress: unreachable sites, thinking in circles, empty replies, cd out of the project, unrun scripts - #139

Merged
nasser1941 merged 1 commit into
mainfrom
fix/stuck-sessions
Oct 15, 2026
Merged

nasser1941 merged 1 commit into
mainfrom
fix/stuck-sessions

Conversation

@nasser1941

@nasser1941 nasser1941 commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Why

A real session spent about 1.5 hours on an Android build (buildozer) and ended with no answer at all. The cause was outside lcode's reach: from that network, Google's Android SDK/NDK repository answers 404 to everything, even its index file. But nothing in lcode noticed, and several things made it worse:

  1. The guessing never tripped the loop breaker. The model tried about 25 different NDK addresses, all 404. The repeat check only counts identical calls, so nothing stepped in.
  2. Its thinking then went round in circles. The last reply was 70,000 characters of 4 paragraphs repeated about 128 times each, until the 32K context was full: roughly 10 minutes behind the spinner.
  3. The empty reply counted as done. The request ended as success, with nothing for the user to read.
  4. One cd /tmp moved lcode there. The session, the project checks and the memory notes all followed, and the session was saved under /tmp, so lcode -c in the project couldn't find it.
  5. The build script was never run. In the request before, the model wrote build_android.sh, never ran it, and called the job done; its first line failed for the user.

What changes

  • Unreachable sites (lcode/unreachable.py):
    • Per request, lcode tracks per site the calls that failed to reach it, and the ones that got an answer. Failures it recognizes: HTTP errors in curl/wget/urllib/requests/npm/web_fetch output, plus the Status: 404 lines a model's own loops print; names that don't resolve; refused or timed-out connections.
    • 3 different failed calls, none worked: a note tells the model to check one address that must exist, and if that fails too, to tell the user what fails and what they can do (another network or VPN, HTTPS_PROXY, a mirror, CI).
    • 2 more: the request stops, with status blocked.
    • A site that answered once in the request is never flagged. Local and private addresses aren't tracked.
    • No escalation for this: a stronger model can't reach the site either.
  • Thinking in circles (repeats.Circles): the thinking is watched as it streams. When 16 of the last 20 sentences have each been seen at least 4 times, lcode closes the stream (which stops the model), drops the reply and asks for a short one.
  • Replies with no answer:
    • An empty reply, a reply that went round in circles, or one cut off by the context (done_reason: length) before saying anything gets one more chance.
    • The second time, lcode offers the stronger model (when one is set), then stops with status loop.
    • An answer cut off midway is kept, with a warning.
    • The OpenAI-compatible backend now reports done_reason from finish_reason.
  • The folder stays put:
    • A cd within the project (its git root, or the folder lcode works in) is kept. One out of it isn't, and the result says so.
    • Agent.workspace is the folder lcode was started in, or moved to with /cd, a resumed session or the bench. Sessions are saved under it, and lcode -c looks it up there.
  • Scripts never run: when a request wrote a shell script (.sh/.bash/.zsh, or a shell shebang) and no command ran it, it's sent back once before the answer counts. chmod, cat or git add don't count as running it. Off with checks = "off".

repeat_limit = 0 also turns off the unreachable-site and circles checks.

Evidence

The failed session, replayed:

  • The site check would have sent its note at the 3rd failing call and stopped the request at the 5th, about an hour before the real session ended.
  • The circles check catches the final reply at character 3,255 of 70,502.

False alarms:

  • Site check: on 50 saved lcode sessions plus 5,319 shell calls from Claude Code transcripts (each transcript counted as one long request, stricter than lcode's per-request reset), the only alerts were in the failed session itself, or in commands that printed its log.
  • Circles check: over 633 other saved replies, it flagged one, and that one was also a real loop (61,000 characters: "updating panel references..." 54 times).

Real model, real network: I gave lcode -p with qwen3.6-35b this task: "Download the Android NDK r27c for Linux from Google into ./ndk…".

  • 0.35.0: 15 minutes, status success, and a confident wrong answer: "r27c does not exist as an official Android NDK release".
  • This branch:
    • the note fired at the 3rd failed call to dl.google.com;
    • the model then checked that www.google.com answers, and fetched the official download pages to get an address that must exist, as the note says;
    • the run hit my 25-minute limit there, before its final answer.
  • Caveat: both runs shared the GPU with another session, which forced model reloads (5 and 7 of them), so I'll rerun the comparison once the GPU is free.

Tests: tests/test_stuck.py (17 tests) covers all five cases, and tests/test_backends.py covers the cut-off signal. The suite and the strict docs build pass.

🤖 Generated with Claude Code

From a real session that spent an hour on an Android build whose downloads
this machine couldn't reach, then ended without a word:

- A site that fails for three different calls, and never answers, gets a
  note (check one address that must exist, then tell the user); two more and
  the request stops (status blocked). Every guessed address differed, so the
  repeat check never fired.
- Thinking that goes round in circles is stopped as it streams and the model
  is asked for its next step (the session's last reply: 70,000 characters of
  four paragraphs, repeated until the context was full).
- An empty or cut-off reply no longer ends the request as done: one more
  chance, then a stop that offers the stronger model when one is set.
- A cd out of the project no longer moves lcode there (the session had been
  saved under /tmp); sessions are saved under the folder lcode works in.
- A shell script written and never run is sent back once before done.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@nasser1941
nasser1941 merged commit 50ac381 into main Oct 15, 2026
12 checks passed
@nasser1941
nasser1941 deleted the fix/stuck-sessions branch October 15, 2026 16:42
@nasser1941 nasser1941 mentioned this pull request Oct 15, 2026
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