Skip to content

Give every top-level expression its file on Julia < 1.5 - #140

Merged
davidanthoff merged 2 commits into
mainfrom
agents/ci-fix-red-legs-main
Oct 3, 2026
Merged

davidanthoff merged 2 commits into
mainfrom
agents/ci-fix-red-legs-main

Conversation

@davidanthoff

@davidanthoff davidanthoff commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Opened by Claude (Claude Code) at David's request, to fix the red CI legs on main.

What was red

Since #138 was merged, main has failed CI on Windows x64, Julia 1.3.1 (both workflows) and 1.4.2 (JSON 0.20 workflow). The failure is intermittent: the 1.4.2 leg passed in the other workflow, and the PR's own run failed on a different one of the two new tests. In each case, one of #138's "a breakpoint added/removed while paused" testitems fails. The debug engine task dies with:

BoundsError: attempt to access String at index [7]
  match(::Regex, ...)            regex.jl
  splitdrive(::String)           path.jl
  joinpath / find_source_file    loading.jl
  CodeTracking.maybe_fix_path
  JuliaInterpreter.framecode_matches_breakpoint
  JuliaInterpreter.Frame(::Module, ::Expr)
  DebugEngines.get_next_top_level_frame

Root cause

This is a real bug on old Julia that those tests were the first to exercise; it is not a test problem.

  • On Julia ≤ 1.4, Base.parse_input_line(code; filename=...) puts the file only in the first top-level LineNumberNode. Every later top-level one has file = nothing. 1.5+ sets it everywhere.
  • Lowering copies that nothing into LineInfoNode.file, which is typed Symbol. JuliaInterpreter reads it as a Symbol, so every top-level frame after the first gets a garbage file name. Locally, on 1.4 with JuliaInterpreter 0.8.21, it came out as "Pb\t"`.
  • Effect without a crash: file breakpoints on top-level lines after the first expression are never hit.
  • Effect with a crash: once any file breakpoint is set, building such a frame matches the garbage name against the breakpoint path. The name isn't absolute, so find_source_file runs Windows' splitdrive regex on the garbage bytes, which can read past the end and throw the BoundsError above. Whether that happens depends on the garbage, which is why the failure is intermittent.

Fix

Right after parsing, run replaces top-level LineNumberNodes whose file is nothing with ones that carry the debuggee's file name. On Julia ≥ 1.5 this changes nothing.

There is a new testitem, "a breakpoint on a later top-level line is hit". It runs x = 1; y = 2; z = 3 with a breakpoint on line 2. It fails on every run on 1.3/1.4 without the fix (the run finishes without stopping), unlike #138's tests, which only catch the bug when the garbage happens to crash.

The new test is skipped on Julia 1.0 and 1.1. Those versions parse top-level code without any line numbers, so a breakpoint on a top-level line has never been able to match there, with or without this fix. Making that work would mean re-parsing the file ourselves to recover line positions, which I left out of scope.

Verified locally

  • Full suite, 171/171 passing, on Julia 1.2, 1.3.1 x64, 1.4.2 x64 (with JuliaInterpreter 0.8.21, as CI resolves) and release.
  • The new test fails on 1.4 before the fix and passes after.

Separate issue noticed (not changed here)

A breakpoint that is hit at the start of a top-level expression is reported with StopReasonStep instead of StopReasonBreakpoint on every Julia version. our_debug_command returns the frame's pc, and run maps any number to a step. The new test therefore checks where the run stops, not the reason. I can fix that in a follow-up if wanted.

🤖 Generated with Claude Code

davidanthoff and others added 2 commits October 3, 2026 11:10
Julia up to 1.4 records the file only in the first top-level line number of
parsed code; every later one has `nothing` as its file. Lowering puts that
`nothing` into the `Symbol`-typed `file` field of a `LineInfoNode`, so
JuliaInterpreter reads garbage as the file of every top-level frame after
the first. File breakpoints on top-level lines past the first expression
were therefore never hit. Worse, once any file breakpoint is set, building
such a frame matches the garbage name against the breakpoint's path; it is
not absolute, so `find_source_file` runs Windows' `splitdrive` regex on it,
which can read past the end and throw a `BoundsError` that kills the engine.

That is what made the Windows x64 legs for Julia 1.3 and 1.4 fail
intermittently since the paused-breakpoint tests were added: those are the
first tests to set a file breakpoint and then reach a second top-level
expression.

The run now fills in the missing files before splitting the code. On Julia
1.5 and later the parser already sets them, so nothing changes there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Those versions parse top-level code without any line numbers, so a top-level
frame has no line a file breakpoint could match, with or without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@davidanthoff
davidanthoff merged commit 622db8e into main Oct 3, 2026
236 of 237 checks passed
@davidanthoff
davidanthoff deleted the agents/ci-fix-red-legs-main branch October 3, 2026 22:04
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