Skip to content

[TH-9448] fix: follow-ups from #224's last review of the browser-agent scan - #288

Merged
khushalsonawat merged 1 commit into
feat/TH-8804-source-subfolderfrom
feat/TH-9448-scan-follow-ups
Nov 11, 2026
Merged

khushalsonawat merged 1 commit into
feat/TH-8804-source-subfolderfrom
feat/TH-9448-scan-follow-ups

Conversation

@khushalsonawat

Copy link
Copy Markdown
Member

Description

These are follow-ups from the last review of #224, the check that refuses browser and computer-use agents (TH-8806), all in agent_type_check.py.

The one with real weight is a log leak, fixed here.

  • The cause: a .py file with a NUL byte after an indented block makes the Python 3.12 and 3.13 tokenizer raise SystemError. The scan's skip handler logged that at WARNING with its full traceback. The traceback quoted the customer's source line holding the NUL.
  • The reach: this got into production logs. The hosted root logger is at INFO, and --read-agent-only falls back to Python's last-resort handler at WARNING.
  • The scale: 20,000 such files gave about 2,850 tracebacks (5 MB) in one scan.
  • The fix:
    • the tokenizer's SystemError is caught with its other errors, at DEBUG;
    • the skip handler logs the error type only, once per scan, plus a count line;
    • the path and the trace go to DEBUG.

Type of Change

  • Bug fix

Changes

  • Item 3, the log leak (above). The only WARNING line left in this file names the job's own subfolder setting (_walked), never a file or a line. credentials.py logs the same at WARNING.
  • Item 1. A folder holding exactly the same package records as one already read shares that folder's rows. They are unread and never copied, for an eighth of each record's size (at least 4 KiB each).
    • 21 copies of a 160-package environment now fit in the 16 MiB record budget, against 9 before.
    • A near copy, with one package more or fewer, is read as before.
    • The agent folder's own walk in the subfolder scan shares too; the records at and above the agent don't.
  • Item 5. The scan's cache key sums every read file's modified and change times, with the count and size. Neither a restored mtime nor a file dated in the future can hide an edit.
  • Items 4 and 10, tests only:
    • a nested folder's records don't take the top's own modules;
    • both 16 MiB record budgets and the 32 MiB Python total are pinned at their real values;
    • a refused-codec file is searched whole, for an import, a marker and the toolkit;
    • a folder holding only a folder inside a test-named folder is read.
  • Ruled out, each pinned by existing tests:
  • The cited review reports (dileepfix3-self-r1/r2) are missing. The items were rebuilt from the code, the TH-8806 ledger and fresh probes.

Known limits (Lows, kept here and not as tickets)

  • Sharing. Two folders whose records match by name and size but differ inside share rows. The worst case is one model question lost or added, never a refusal, and a crafted repo can already hide any file it names.
  • Memory. Sibling folders with different records keep their rows for the whole walk, which stays within the 16 MiB rows bound: 16 different 1 MiB siblings peaked at 162 MB, against 24 MB before. Exact copies use less than before.
  • Overdraw. A shared folder whose charge overdraws the budget returns no rows, where the old code read part of it. That means more files are scanned, at worst one extra question.
  • One defence no test can reach. Pass 2's whole = False on the budget break can't change an outcome, because no later folder can match once the budget is spent.
  • The cache key on Windows. The modified-time half of the key matters only where the change time is the creation time. CI has no Windows job.
  • Catches the tests don't pin:
    • how broad the skip handler's except Exception is;
    • the older ValueError and SyntaxError in the tokenizer catch.
      Narrowing either adds at most a type-only WARNING, never a leak. P3a (dropping SystemError) is caught on 3.12 and 3.13 only, because 3.14 raises TokenError there.
  • SyntaxWarnings. The shared reader's SyntaxWarnings still print to stderr, naming <unknown>, with no path or line. That is the shared reader's, in fix(harness): decode a Python file before searching it when its codec can spell a name (TH-9490) #276 and fix(harness): give a browser scan its limits cut a second look (TH-9512) #281.

Testing

test file passed
test_agent_type_refusal 678 (651 + 27 new)
test_contract_tools_subfolder 180
test_executor_completed_counts 8
test_agent_reading 105
test_builder_chat 60
test_hosted_authoring_entrypoint 12
test_harness_architecture 55

Each file ran on its own, and none failed.

Planted bugs: 35 one-token or one-line breaks, each run in a scratch copy with stale bytecode cleared, each turned its named test red. The reviewers re-ran them on the final code and added their own.

Stability: the timing and memory rows passed 10 of 10 repeat runs.

Review:

  • a design review, two rounds (the first was NOT CLEAR, on a test that was flaky on Linux);
  • a plan review, one round;
  • one change review round;
    each by two fresh reviewers. All the final rounds were CLEAR, with nothing above Low in the change review.

Overlap with #281 (TH-9512)

Related Issues

Refs: TH-9448

… scan (TH-9448)

- A WARNING line no longer prints a customer's source line or a file path.
  - A .py file with a NUL byte after an indented block made the 3.12 and
    3.13 tokenizer raise SystemError. The scan's skip handler logged it at
    WARNING with its full traceback, which quoted the line holding the NUL.
  - The tokenizer's SystemError is now caught with its other errors, at
    DEBUG.
  - The skip handler logs the error type only, once per scan, plus a count
    line. The path and trace go to DEBUG.
- A folder holding exactly the same package records as one already read
  shares that folder's rows, unread and never copied, for an eighth of
  each record's size (at least 4 KiB each). 21 copies of a 160-package
  environment now fit in the 16 MiB record budget, against 9 before.
- The scan's cache key sums every read file's modified and change times,
  with the file count and size. Neither a restored mtime nor a file dated
  in the future can hide an edit any more.
- New tests pin:
  - the top folder's own modules;
  - both 16 MiB record budgets and the 32 MiB Python total, at their real
    values;
  - a refused-codec file searched whole;
  - folders that hold only a folder inside a test-named folder.
- Ruled out, each pinned by existing tests: items 2, 6, 8 and 9. Item 6b's
  turn-limit bound is now in HOW-IT-WORKS.md.

Reviewed:
- a design review (two rounds);
- a plan review;
- one change review round;
each by two fresh reviewers, the last of each round CLEAR.

Refs: TH-9448
Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
@khushalsonawat khushalsonawat self-assigned this Nov 11, 2026

@cdileep23 cdileep23 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the comments.

Comment thread src/fi/alk/harness/agent_type_check.py Resolved
Comment thread src/fi/alk/harness/agent_type_check.py Resolved
@khushalsonawat
khushalsonawat merged commit bc5a363 into feat/TH-8804-source-subfolder Nov 11, 2026
11 checks passed
@khushalsonawat
khushalsonawat deleted the feat/TH-9448-scan-follow-ups branch November 11, 2026 05:56
khushalsonawat added a commit that referenced this pull request Nov 11, 2026
…288) into TH-9491

- tests/test_harness_sandbox_server.py: both sides appended tests; kept both.
- test_contract_writer's allow-list gains #269's two contract.json copies:
  reading_result.document's replace_if_unchanged (the saved contract's own bytes
  plus the reading record) and seed_contract.use_seed's _write_whole (the
  platform's seed, as written).
- sandbox_server.py imports SecretResolutionError, which #284's resolver raises
  but the base never imports: seven sandbox-server tests fail with NameError on
  the base itself.

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
khushalsonawat added a commit that referenced this pull request Nov 11, 2026
…total

Brings in #269, #276 (through #283), #283 and #288. Three hunks against
#288 (TH-9448) were resolved by hand, keeping both intents:
- a skipped file logs its type once at WARNING, and its path and trace
  at DEBUG (#288); it is also counted as the scan's `skipped` cut, and
  that count is in the cut line, so #288's separate "skipped N files"
  line is not repeated (TH-9512);
- #288's skip tests and its NUL-byte test now expect the cut line, which
  holds names and counts only.

Tests on 3.12.3: test_agent_type_refusal 720, test_browser_scan_cut 59,
test_contract_tools_subfolder 180, all passed.

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
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.

2 participants