Skip to content

fix(interp): charge tilde expansion to the execution budget - #2672

Merged
chaliy merged 5 commits into
mainfrom
codex/tilde-expansion-budget
Oct 11, 2026
Merged

chaliy merged 5 commits into
mainfrom
codex/tilde-expansion-budget

Conversation

@chaliy

@chaliy chaliy commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

What changed

Tilde expansion (~user, x=~, x=~:~, ${u-~}) now charges the shared execution budget. Previously tilde_expand_literal built results with an unbudgeted String, so a script stacking tildes could grow output without ever tripping budget limits.

Why

Execution-budget E5 requires every interpreter allocation site to charge budget. Unbudgeted tilde expansion is a resource-exhaustion hole (TM-DOS-134): each : operand re-resolves the home directory for free.

Before / After

New regression tests fail on the old code (5 of 7 fail without the fix; the 2 parity tests pass both ways) and pass with it:

  • cargo test -p bashkit --test integration execution_budget: 32/32 pass
  • CLI smoke: echo ~; x=~; echo "$x"; echo ~root; echo ~/sub; v=${u-~}; echo "$v" expands correctly (sandbox homes by design)
  • Differential check: same script under real bash expands identically in structure

Risk

  • Low. Fail-closed accounting only; expansion results unchanged (covered by a bash-parity test). No API changes outside the interpreter.

Checklist

  • Tests added or updated (7 tilde budget tests: exhaustion, boundary, leases, parity)
  • Backward compatibility considered (internal code; no compat needed)

tilde_expand_literal built its result with String::new + push_str, so a script with many tildes (x=~:~:~:...) could grow output without ever touching ExecutionBudget (TM-DOS-134). Route all tilde paths through BudgetedString, hold declaration leases across expansion, and cover it with budget regression tests.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit 91997c5 Commit Preview URL

Branch Preview URL
Oct 11 2026, 06:54 AM

Move the BudgetedString temporaries out of expand_word_buffer's Literal arm into append_literal_segment, so each $(...) nesting level's poll frame stays small. Measured depth-32 stack threshold restored to the pre-tilde-change baseline (pass at 1968 KiB, abort at/below 1952 KiB); the inline version cost 16-32 KiB and aborted the coverage job under tarpaulin instrumentation.
@chaliy

chaliy commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

The current CI watcher has a concrete completion bug, separate from CI status.
Its live shell85598 (parent55931) for task_01a128c43e2d730782727722aa906bc0
sets HEAD_SHA=$(git rev-parse HEAD), then requires that full SHA appear in the
plain gh pr checks 2672 output before checking pending counts. The actual
output contains check names, states, durations and URLs, not the full head SHA.
At head afc0f81 the predicate never matches;
the loop can sleep for all120iterations even when CI is terminal.

Please cancel this exact still-live faulty watcher through supported cancel_task
and wait until it settles; do not repeat cancellation of old dead-owner orphan
records or modify session/task databases. Preserve the healthy agent and other
work. Observe headRefOid separately via structured gh pr view, then use bounded
structured check polling or gh pr checks --watch; independently inspect final
GitHub conclusions and real producer exit, not an echo/tail pipeline status.
Reuse PR2672 and the original branch/session. The new head is currently under
fresh CI, not yet shipped. Wait for all required green checks and resolved
reviews, squash merge, rebuild the actual merge SHA and verify the original
tilde-budget attack and matched-environment finite semantics with durable bounds.

No stack-size waiver, force push, test/CI weakening, model change or duplicate
agent/PR. Do not end with any new live background watcher. Keep unresolved
orphan-runtime bookkeeping separate from truthful product proof.

@chaliy

chaliy commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Current afc0f81 still fails actual Linux
coverage: https://github.com/everruns/bashkit/actions/runs/38104908986/job/114368236165.
At02:27:35 the log reports depth_32_no_stack_overflow stack overflow and
process abort. The extraction did not resolve the required coverage failure.
Full independently retrieved log is saved in the job's
coverage-afc0f819-failed.log; earlier red revisions remain preserved.

Use a changed, matched diagnosis: macOS instrumented thresholds alone do not
prove Linux/tarpaulin frame safety. Compare clean current-main and this branch
under the actual failing instrumentation/feature/compiler path in a bounded
child, investigate remaining per-level async-frame growth, and fix the cause.
Do not retry unchanged CI, raise stack sizes, suppress the test or waive checks.
The distinct macOS misconfig_huge_ast_depth_still_safe failure must not be
used to dismiss this Linux depth32 failure. Keep original tilde budget/finite
semantics tests and all required shipping gates.

Monitor stopped ONLY the confirmed faulty SHA-gated watcher group85598 and
its sleep child, after validating exact owner55931, group and command; no agent,
build, CI job or session database was changed. task_01a128c43e2d730782727722aa906bc0
now honestly settled failed at02:37:29, and your live session resumed analysis.
Receipt: watcher-stop-receipt.json. Do not recreate the same impossible
SHA predicate or relabel that watcher as a CI success. Read the earlier watcher
review and all PR comments in full, use supported structured head/check polling
with real statuses, and continue in the original session/branch/PR2672.
No merge or finding closure until actual greenCI and merged-revision proof.

@chaliy

chaliy commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Current working-tree review (04:03 UTC), before any new push:

The plain-String redesign of tilde_expand_literal replaces BudgetedString::try_push_str with String::push_str, and only leases expanded.len() in callers after expansion has already allocated. That reintroduces the original finding: HOME/directory length multiplied by tilde count can allocate far beyond the live-memory budget before the later lease rejects. consume_work is not a live-memory reservation. The new comment claims reservation before allocation, but the code no longer does it. The operand suffix at expansion.rs around2281 also grows before leasing.

Do not ship this scratch-allocation workaround or weaken its regression to repair stack coverage. Keep budget reservation before every growth and retain the lease while scratch/output coexist. Reduce stack frames by preserving the existing checked builder behind a smaller synchronous/heap-indirected boundary or another repository-consistent design, rather than allocating first and accounting afterward. Add bounded evidence that low live-memory limits prevent the original repeated-tilde amplification before allocation, plus finite compatibility and the still-required green Linux instrumented depth32 gate. Assembly frame size and macOS depth50 baseline do not waive either requirement. Preserve working changes and diagnostics; no model change, hook bypass or duplicate PR.

append_literal_segment only fails with budget exhaustion, so return the small limit type directly instead of the shared Error. Keeps the hot poll frame lean (x86_64 closure sub rsp 1320 -> 1288; base 1144) with identical Error::from(LimitExceeded) semantics at the single conversion point. No accounting change: budget/stack suites green, depth-32 threshold unchanged.
@chaliy
chaliy merged commit 6934199 into main Oct 11, 2026
49 checks passed
@chaliy
chaliy deleted the codex/tilde-expansion-budget branch October 11, 2026 07:22
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