Skip to content

fix(build_ledger): unclosed file handle and module-level open() at import time crashes automation #274

Closed

Description

@piyushmakhija28

Summary

docs/execution/build_ledger.py has two related bugs: (1) json.load(open(ledger_path)) at line 213 uses open() without a with statement, leaking a file descriptor on Windows; (2) open(SRC) at line 20 runs at module import time with no error handling — if the source file is missing, any import of the module (including tests) crashes with FileNotFoundError.

Root Cause

Bug 1 — build_ledger.py around line 213:

data = json.load(open(ledger_path))   # ← file handle never explicitly closed

On Windows the process holds an exclusive lock until GC collects the file object. Repeated calls in a CI loop accumulate leaked handles until OSError: [WinError 32] blocks subsequent writes.

Bug 2 — build_ledger.py around line 20:

SRC = 'docs/orchestration_prompt.md'
PROMPT = open(SRC).read()   # ← runs at import time, no error handling

Any automation that imports this module in a fresh checkout where orchestration_prompt.md doesn't yet exist crashes with FileNotFoundError at import time, bringing down the entire process (not just this function).

Failure Scenario

Bug 1: CI runs build_ledger.py 100 times in a pipeline loop. File descriptor leak accumulates. On iteration ~500 (OS limit), OSError: [WinError 32] blocks any further write to the ledger file. CI appears to hang with no informative error.

Bug 2: A test suite imports build_ledger to test ledger structure. docs/orchestration_prompt.md doesn't exist in the test environment. FileNotFoundError is raised at import time. Pytest marks the entire test file as a collection error, hiding all other test results.

Proposed Fix

Fix Bug 1:

with open(ledger_path) as f:
    data = json.load(f)

Fix Bug 2:

def _load_prompt(src: str) -> str:
    try:
        with open(src) as f:
            return f.read()
    except FileNotFoundError:
        return ''   # or raise with a clear message

PROMPT = _load_prompt('docs/orchestration_prompt.md')

Or lazy-load PROMPT on first use:

_PROMPT: str | None = None

def get_prompt() -> str:
    global _PROMPT
    if _PROMPT is None:
        with open('docs/orchestration_prompt.md') as f:
            _PROMPT = f.read()
    return _PROMPT

Activity

  1. piyushmakhija28 commented on Jun 21, 2026

    @piyushmakhija28
    ContributorAuthor

    Fixed in PR #280 (Sprint R3/R4: fix(sprint-r3): root-cause fixes for all 39 bugs from code review round 3 + Sprint R4 round 4)

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

    bugSomething isn't workingcode-reviewFound via automated code reviewpriority: mediumFunctional error with workaround

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions