* test: fix five Windows-host failures across sandbox, read_file, lark shim, and extension checks
Windows dev hosts currently fail five backend test cases that CI (ubuntu)
never exercises:
- test_local_sandbox_command_timeout: the fixture forces a bare /bin/sh
shell and the Windows dispatch spawns it directly (WinError 2), and
make(1) is not a given on Windows. Stub both runners; the contract under
test is the timeout marker assembly, not the runner.
- test_local_sandbox_provider_mounts: the Popen-capture mock delegated to
the real spawn, which cannot execute a POSIX shell path on Windows.
Capture at the runner seam instead and assert the rewritten path
shape-agnostically (the Windows runners pass the command string, not a
[shell, -c, command] list).
- test_read_file_tool_binary: text-mode write_text leaks CRLF into the
fixture on Windows; write the mount fixture as bytes (#6042 follow-up).
- test_lark_shim_atomic: skip the exact 0o755/0o644 assertions on Windows
hosts, whose stat mode approximates POSIX bits (#6257 pattern).
- test_extension_check_script: zipfile normalizes os.sep in entry names on
Windows reads, so a writestr-built fixture can never carry a verbatim
backslash entry there. Inject the hostile name into namelist unless the
archive already stores it, pinning the guard contract identically.
On a Windows 11 host these five files go from five failed cases to green
(102 passed, ruff check/format clean). Two more Windows-host failures
(skill-review NUL probe, jina shared-deadline classification) need deeper
root-causing and are left for follow-ups.
* test(skill-review): review the committed skill tree, not local residue
The video-generation review test scans the live directory, so untracked
build residue inside skills/public/video-generation fails it: once bytecode
caching drops a scripts/__pycache__ directory into the tree (importing the
skill script writes one), skillscan's package-undecodable-script finding is
correct and the test can never pass again until the residue is deleted by
hand. Reproduced on Windows: a planted scripts/__pycache__ pyc fails the
test; deleting it makes it pass.
- Materialize the tracked skill tree (git ls-files) into a temp dir and
review that, so the test reviews exactly the committed package.
- tests/skills/skill_loader.py disables bytecode caching around
exec_module, so the root skill suite stops polluting the committed tree.
Follows up on #6786 (item 6).
* docs(test): state the argv-list contract on both sandbox runners
The Windows dispatch also passes the resolved command as an argv list, so
the string fallback was unreachable on every platform.
---------
Co-authored-by: xiaodu55 <xiaodu55@users.noreply.github.com>
* fix(middlewares): include file line count in read-before-write block message and clarify read_file range error
* fix(middlewares): address review feedback on line count pluralization and readability
* fix(middlewares): address round-2 review feedback for PR #6020
Stamping bug (_attach_read_mark, ggnnggez item 1):
Originally planned as a standalone issue; folded here since it is the exact mechanism behind the #6019 gate bypass. _attach_read_mark now returns early when message.content is in READ_FILE_NO_CONTENT_RESULTS - {READ_FILE_EMPTY}. In-band errors (invalid range, start_line exceeds file length, etc.) are returned with status='success' by LangChain so the existing message.status == 'error' guard did not catch them.
HTTP code collision [P3] (willem-bd):
Pre-stamp TOOL_META_KEY on the blocked ToolMessage before normalize_tool_result so files with 401, 403, 404, or 500 lines are not misclassified as auth/server errors by _classify_error_text. normalize_tool_message returns early when an existing TOOL_META_KEY entry is found.
Line-count parity (ggnnggez, inline L317):
Replace splitlines() with count_file_lines() in read_file_contract.py using count('\n'), matching LocalSandbox text-mode iteration and the _truncate_read_file_output formula. splitlines() over-counts for files with \f or \u2028 separators (e.g. pdftotext output).
Read hint (ggnnggez, inline _BLOCK_MESSAGE):
For empty files the ranged-read suggestion is omitted. For non-empty files the hint gives a concrete start_line=max(1, N-29), end_line=N.
Constant rename (ggnnggez, read_file_contract.py):
READ_FILE_INVALID_RANGE is the new canonical name; READ_FILE_EMPTY_RANGE kept as a backward-compat alias. _LEGACY_READ_FILE_EMPTY_RANGE removed (unreachable after the #6019 contract update).
Tests:
- TestBlockMessageLineCount parametrized with \f and \u2028 parity cases
- TestStampingGateBugFix: 4 no-content variants, gate-closed integration probe, empty-file-still-stamps assertion
- test_gate_block_on_numeric_line_count_is_recoverable: 401/403/404/500
- test_contract_constants_membership replaced with test_contract_constants_parity_with_localsandbox
---
NOT INCLUDED: ggnnggez item 2 (file length in read_file range error strings). READ_FILE_NO_CONTENT_RESULTS uses exact string membership and is also consumed by build_skill_usage for skill-read detection; making those strings dynamic requires a dedicated contract refactor. Deferred to a follow-up PR.
Note: test_web_fetch_relative_links failures in the full suite are pre-existing on upstream/main and unrelated to this change.
* test: clarify blank ranged reads in the write gate
---------
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
read_file decodes with UTF-8. Binary uploads (.xlsx, images, ...) raise
UnicodeDecodeError in the sandbox layer. UnicodeDecodeError is a ValueError
subclass, not an OSError, so it bypassed the typed handlers and fell through
to the generic except, surfacing a vague "Unexpected error reading file"
message. The model could not tell the file was binary, so it retried
read_file instead of switching to bash + pandas/openpyxl, burning LLM
round-trips and bloating context with repeated failures.
Add a dedicated UnicodeDecodeError handler that tells the model the file is
binary and to use bash with a suitable library (or view_image for images).