Skip to content

feat(capi): expose host-directory mounts over the C ABI - #2371

Merged
chaliy merged 8 commits into
everruns:mainfrom
tersePrompts:capi-host-mounts-pr
Sep 5, 2026
Merged

feat(capi): expose host-directory mounts over the C ABI#2371
chaliy merged 8 commits into
everruns:mainfrom
tersePrompts:capi-host-mounts-pr

Conversation

@tersePrompts

@tersePrompts tersePrompts commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

What changed

The C API can now mount real host directories into a session, with an
allowlist-first safety model that is at least as strict as the JS/Python
bindings:

  • Config v1 gains optional mounts: [{path, root, writable}] and
    allowed_mount_paths. Mounts are applied after build via the existing
    live-mount API (Bash::mount with RealFs wrapped in PosixFs), so
    readonly_filesystem continues to wrap mounted filesystems.
  • Two new exports, bashkit_mount / bashkit_unmount, attach and detach
    host directories on a running session. Shell state (vars, cwd, history) is
    preserved across both, matching live-mount semantics elsewhere.
  • Every mount root must resolve under an allowed_mount_paths prefix. Roots
    are canonicalized before the prefix check, so .. segments and symlinks
    cannot escape an allowlisted prefix; comparison is case-folded on Windows.
    No allowlist configured → every mount is rejected.
  • TM-FS-013 sensitive-path denylist (review follow-up): every mount root
    (config-time and runtime) must also clear the shared denylist, now exposed
    as bashkit::is_sensitive_mount_path. A sensitive root (home trees, /etc,
    .ssh, ...) additionally requires an allowlist entry that names it
    exactly — a broad parent entry such as the home directory itself is not
    consent to expose credential stores. This is deliberately stricter than the
    builder/JS live-mount precedent (where any covering entry overrides the
    denylist); the decision is recorded in knowledge/runtimes/c-api.md and the
    TM-FS-013 row of the threat model.
  • capabilities_json gains the realfs-mounts feature marker so embedders
    can feature-detect instead of failing on config parse.
  • bashkit.def and include/bashkit.h are extended additively — all ABI v1
    signatures are unchanged.

Why

bashkit-capi is the FFI surface for every non-Rust embedder, but it only
exposed the in-memory VFS — while bashkit-js and bashkit-python expose
real filesystem mounts through direct crate bindings. C ABI consumers (Java
via JNA, C/C++, others) currently cannot use live mounts at all. This brings
the C API to parity and makes the allowlist mandatory, not optional.

Before / After

Before, a config containing mounts is rejected outright by
deny_unknown_fields (invalid configuration), and there is no way to
expose a host directory through the C API.

After (crates/bashkit-capi/tests/abi.rs, six new tests, all green):

  • config with mounts + allowed_mount_pathscat /data/note.txt
    returns the host file's content
  • write into a read-only mount fails inside the shell and the host file
    provably never appears
  • bashkit_mount → exec sees the files → bashkit_unmount → paths fall
    back, round trip clean
  • mount root outside every allowlisted prefix → rejected at config time and
    runtime
  • sensitive root (.ssh) under a broad allowlist entry ($HOME) → rejected
    at config time and at runtime, path stays unresolved (review PoC)
  • sensitive root named exactly in the allowlist → mounts (explicit consent)

Risk

  • Low–Medium. Purely additive to the ABI: new exports, new optional config
    keys, no signature changes. Old configs stay valid on new binaries; old
    binaries reject only the new keys (same deny_unknown_fields behavior as
    any unknown field today).
  • The mount checks are the sensitive part. Canonicalization falls back to
    the lexical path when canonicalize fails (root doesn't exist yet); in
    that case RealFs::new fails immediately after, so no unverifiable root
    is ever mounted. Windows case-insensitivity handled by path folding.

Checklist

  • Tests added or updated — 6 new ABI tests in crates/bashkit-capi/tests/abi.rs
  • Backward compatibility considered — additive only; existing configs and exports unchanged

Bring the C API to parity with the JS/Python bindings' real filesystem
mounts, behind the same safety model:

- config v1 gains optional mounts: [{path, root, writable}] and
  allowed_mount_paths; mounts are applied after build via the live
  Bash::mount API (RealFs wrapped in PosixFs), and readonly_filesystem
  continues to wrap mounted filesystems
- new bashkit_mount / bashkit_unmount exports attach and detach host
  directories on a running session, preserving shell state
- every mount root must resolve under an allowed_mount_paths prefix;
  roots are canonicalized before the prefix check so '..' segments and
  symlinks cannot escape, and comparison is case-folded on Windows
- capabilities_json gains the realfs-mounts feature marker so embedders
  can feature-detect support
- bashkit.def and include/bashkit.h extended additively; ABI v1
  signatures are unchanged
- three new ABI tests: read-only mounts (host file provably absent after
  denied writes), runtime mount/unmount round trip, and allowlist
  enforcement (missing allowlist and out-of-prefix roots rejected)
@tersePrompts

Copy link
Copy Markdown
Contributor Author

Context that might be useful for review: this patch is already driving a complete Java binding for the C API — Bashkit4j (MIT, io.github.terseprompts:bashkit4j on Maven Central). It exposes these mount exports through a Java builder API (.allowMountsUnder(...) / .mount(...)), runs its 5-platform native builds from this branch, and its end-to-end test suite (read-only enforcement, writable round trips, live mount/unmount, traversal containment) passes on Windows, Linux and macOS.

If it fits the project, we'd be glad to see the Java binding mentioned wherever you list integrations or language bindings — but that's entirely your call, no expectations. Either way, happy to adjust the patch however review requires.

@chaliy chaliy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the contribution — the ABI additions (bashkit_mount/bashkit_unmount, additive .def/.h changes, capability marker) are clean and the round-trip/allowlist tests are a good start. Two blockers found by building this branch directly and adding a couple of verification tests:

  1. Security gap vs. stated parity (validate_mount_root in crates/bashkit-capi/src/lib.rs): the config-time mounts + allowed_mount_paths path bypasses the existing TM-FS-013 sensitive-path denylist (.ssh, .aws, .kube, .docker, .gnupg, .gcloud, /etc, /root, /home, …) that bashkit-js/bashkit-python inherit for free by routing through BashBuilder::mount_real_readonly_at + .allowed_mount_paths(...). I confirmed with a PoC test that allowlisting a home-style directory and mounting its .ssh subdirectory exposes private key contents through the shell — something the existing builder API already refuses in the equivalent scenario. See inline comment for the repro and suggested fix (route through the builder, or reuse is_sensitive_mount_path). The new THREAT[TM-SBX-XXX] code comment also isn't backed by a real, registered entry in knowledge/security/threat-model.md (TM-SBX doesn't exist yet, and XXX is a literal unfilled placeholder) — this should extend TM-FS-013 instead.

  2. Breaks the mandatory clippy gate: RealFs::new is #[deprecated]; both new call sites lack the #[allow(deprecated)] the one existing sync call site uses. Confirmed cargo clippy --all-targets --all-features -- -D warnings fails to compile on this branch as-is.

Happy to take another pass once these are addressed. (Separately, re: the linked Java binding / Bashkit4j mention in the top-level comment — that's a call for the maintainers, not a review blocker.)

}
let path = PathBuf::from(root);
let canonical = std::fs::canonicalize(&path).unwrap_or(path);
let candidate = fold_path(&canonical.to_string_lossy());

@chaliy chaliy Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

validate_mount_root only checks that the (canonicalized) root falls under an allowed_mount_paths prefix — it never calls the existing Bash::is_sensitive_mount_path / SENSITIVE_MOUNT_PATHS / SENSITIVE_PATH_COMPONENTS denylist that apply_real_mounts enforces for the mount_real_readonly_at/allowed_mount_paths builder path used by the JS and Python config-time bindings (see crates/bashkit/src/lib.rs around L3418-3462, threat TM-FS-013).

Concretely: an embedder that sets allowed_mount_paths: ["/home/user"] (a very natural, broad allowlist entry) and then mounts /home/user/.ssh gets refused by mount_real_readonly_at + allowed_mount_paths (JS/Python config path), but is accepted here. I verified this with a PoC test added on top of this branch:

#[test]
fn poc_sensitive_subdir_not_blocked_by_config_allowlist() {
    unsafe {
        let home = temp_dir("poc-home");
        let ssh_dir = home.join(".ssh");
        std::fs::create_dir_all(&ssh_dir).unwrap();
        std::fs::write(ssh_dir.join("id_rsa"), b"PRIVATE-KEY-BYTES").unwrap();

        let config = serde_json::json!({
            "schema_version": 1,
            "allowed_mount_paths": [home.to_string_lossy()],
            "mounts": [{"path": "/data", "root": ssh_dir.to_string_lossy()}],
        }).to_string();

        let mut bash = ptr::null_mut();
        let mut error = ptr::null_mut();
        assert_eq!(bashkit_create_json(bytes(config.as_bytes()), &mut bash, &mut error), BashkitStatus::Ok);

        let mut result = ptr::null_mut();
        bashkit_execute(bash, bytes(b"cat /data/id_rsa"), &mut result, &mut error);
        assert_eq!(borrowed(bashkit_result_stdout(result)), b"PRIVATE-KEY-BYTES"); // passes today
    }
}

This reads .ssh/id_rsa straight through the mount. The PR description calls this "parity" with JS/Python, but for config-time mounts it's actually weaker than both: it drops TM-FS-013's defense-in-depth entirely and relies solely on the caller's own allowed_mount_paths precision.

Two asks:

  1. Route apply_config_mounts through BashBuilder::mount_real_readonly_at/mount_real_readwrite_at + .allowed_mount_paths(...) (like bashkit-js/bashkit-python already do for their JSON/dict config), or otherwise call is_sensitive_mount_path (needs to become pub(crate)/exported) from validate_mount_root so config-time mounts get the same denylist.
  2. bashkit_mount (the live/runtime export) matches the existing live-mount precedent in bashkit-js's enforce_mount_policy (also allowlist-only, no sensitive-path check), so that asymmetry may be intentional/pre-existing — but please confirm that's a deliberate "explicit runtime call = informed consent" design choice and not an oversight, since it's not stated anywhere.

Either way, the new THREAT[TM-SBX-XXX] comment introduces a brand-new, never-registered threat ID (XXX is a literal placeholder, and TM-SBX doesn't exist in knowledge/security/threat-model.md) instead of referencing/extending TM-FS-013, which already covers exactly this class of issue for RealFs mounts. Per this repo's knowledge contract, a security-relevant behavior change needs a real entry in knowledge/security/threat-model.md, not an ad hoc placeholder ID left in code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in bd4f257. Two notes on the approach:

  1. I went with reusing the denylist rather than routing through the builder, and one premise turned out to differ from the code: apply_real_mounts only consults is_sensitive_mount_path when no allowlist is configured (crates/bashkit/src/lib.rs, the else if is_sensitive branch). With an allowlist set, a covering entry mounts even a sensitive path — so the Rust builder, bashkit-js (enforce_mount_policy is allowlist-only), and bashkit-python all accept exactly your PoC scenario (allowlist ~, mount ~/.ssh). Routing through the builder would therefore not have blocked the repro.

  2. validate_mount_root now calls the denylist on the canonical root for both config-time and runtime mounts, with an explicit-consent rule: a sensitive root additionally requires an allowlist entry that names it exactly — a broad parent entry (the home directory itself) is not consent to expose .ssh. Naming the sensitive root itself in the allowlist still mounts it. This is deliberately stricter than builder/JS; recorded in knowledge/runtimes/c-api.md and the TM-FS-013 row. is_sensitive_mount_path is now a public free function (bashkit::is_sensitive_mount_path) so the denylist has a single home and embedders can reuse it.

Regressions added in crates/bashkit-capi/tests/abi.rs: your PoC scenario at config time, the same refusal at runtime (mount refused, path unresolved), and the exact-entry consent case.

} else {
RealFsMode::ReadOnly
};
let fs = RealFs::new(&root, mode).map_err(|error| {

@chaliy chaliy Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RealFs::new is #[deprecated] ("blocks on host filesystem I/O; use RealFs::open(...).await"). This PR's two new call sites (here and the bashkit_mount one below) don't suppress it, so cargo clippy --all-targets --all-features -- -D warnings — which this repo's CI/pre-PR checklist runs — fails to compile:

error: use of deprecated associated function `bashkit::RealFs::new`: blocks on host filesystem I/O; use RealFs::open(...).await
   --> crates/bashkit-capi/src/lib.rs:270:26
error: could not compile `bashkit-capi` (lib) due to 2 previous errors

(Confirmed by running clippy against this branch.) The one existing sync call site in crates/bashkit/src/lib.rs (apply_real_mounts) suppresses this deliberately with #[allow(deprecated)] // BashBuilder::build is intentionally synchronous. — please do the same here with a matching justification, or move these onto the async RealFs::open path via the session's tokio runtime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in bd4f257: both RealFs::new call sites now carry #[allow(deprecated)] with the same justification as apply_real_mounts — the C ABI boundary is synchronous and has no async context at these calls.

- expose bashkit::is_sensitive_mount_path as a public free function so
  embedder-side mount policies share one denylist with the builder
- validate_mount_root now applies that denylist on the canonical root for
  both config-time mounts and bashkit_mount: a sensitive root (home trees,
  /etc, .ssh, ...) is only mountable when an allowlist entry names it
  exactly — a broad parent entry such as the home directory is not consent
  to expose credential stores
- suppress the RealFs::new deprecation at both C-API call sites with the
  same justification as apply_real_mounts (synchronous FFI boundary)
- replace the unregistered THREAT[TM-SBX-XXX] comment with TM-FS-013 and
  extend the threat-model row plus the C-API knowledge entry
- regression tests: sensitive subdir refused at config time and runtime
  under a broad allowlist entry; exact-entry consent still mounts
@tersePrompts

Copy link
Copy Markdown
Contributor Author

Both blockers from the review are addressed in bd4f257:

  1. TM-FS-013 denylist: validate_mount_root now consults bashkit::is_sensitive_mount_path (hoisted to a public free function so the denylist has one home) on the canonical root, for config-time mounts and bashkit_mount alike. One correction to the review's premise, detailed in the inline reply: the builder (and JS/Python) currently mount a sensitive path whenever any allowlist entry covers it — the denylist there only fires with no allowlist at all. So this PR now goes a step further with an explicit-consent rule: a sensitive root requires an allowlist entry that names it exactly; a broad parent entry (e.g. $HOME) is refused. The THREAT[TM-SBX-XXX] placeholder is gone; the TM-FS-013 row and knowledge/runtimes/c-api.md document the rule and the deliberate divergence from the JS live-mount precedent.

  2. Clippy gate: both new RealFs::new call sites carry #[allow(deprecated)] with the same justification as apply_real_mounts (synchronous FFI boundary, no async context).

Regressions added: the review's PoC (config time + runtime) and the exact-entry consent case. CI should confirm fmt/clippy/tests on this commit.

tersePrompts and others added 3 commits September 5, 2026 18:17
Adds bashkit_cancel / bashkit_clear_cancel backed by the interpreter's
shared cancellation token, kept outside the state mutex so cancel stays
lock-free while bashkit_execute is blocked. A cancelled execution
reports the new BASHKIT_CANCELLED (7) status, and the capabilities JSON
gains a "cancellation" feature so bindings can feature-detect.

Tests cancel a pending sleep: the request budget polls the token while
the command is in flight, whereas loop-based scripts race the profile's
command/iteration caps before the flag lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The re-landed mounts work dropped the workflow_dispatch lib builder that
Bashkit4j packaging uses; upstream's c-api-binaries workflow only builds
from release tags.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cancellation only lands at command boundaries, so a cancelled sleep is
not interrupted until the profile deadline ends it 30s later. Loop over
1-second sleeps instead: a boundary every second, negligible budget.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chaliy chaliy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-reviewed after the latest push (3e3caa6). Good news first: both blockers from the first round are properly fixed, and I verified this by actually building the branch and running the tests, not just reading the diff.

  • TM-FS-013 sensitive-path denylist: now enforced for both config-time and runtime mounts via a shared bashkit::is_sensitive_mount_path, with an exact-allowlist-entry consent rule that's genuinely stricter than the pre-existing builder/JS precedent. My original .ssh-under-$HOME PoC is now correctly rejected — confirmed locally. Nice fix, and appreciated that it's more conservative than what I asked for, not just the minimum.
  • Deprecated RealFs::new breaking -D warnings: fixed with #[allow(deprecated)] matching the existing convention. cargo clippy -p bashkit-capi -- -D warnings is clean.

Two new things came in with this push that need attention before merge:

  1. New clippy failure (crates/bashkit-capi/tests/abi.rs:367): the new cancellation test has a redundant nested unsafe block that fails cargo clippy -p bashkit-capi --all-targets -- -D warnings (the flag this repo's CI/pre-PR checklist actually uses). Trivial fix — see inline comment. Functionally the test (and everything else) passes; cargo test -p bashkit-capi is 19/19 green.
  2. New workflow file .github/workflows/build-native-libs.yml duplicates the existing c-api-binaries.yml, uses unpinned/mutable action tags where every other workflow here pins to a commit SHA, and skips the permissions: block the rest of CI sets. It reads as infra for the external Bashkit4j binding's own builds rather than something this repo's C API tests need. Suggest dropping it from this PR — see inline comment for details.

Also flagged (non-blocking): bashkit_cancel/bashkit_clear_cancel is a second, unrelated feature riding along in a PR titled around host mounts. The implementation itself is solid and well-tested, so not blocking on it, but consider splitting future PRs like this.

Nice iteration overall — the mount security model is now in good shape. Once the clippy nit is fixed and the workflow file question is resolved, this looks close to mergeable from my side.


Generated by Claude Code

Comment thread crates/bashkit-capi/tests/abi.rs Outdated
let writer = observed.clone();
let worker = std::thread::spawn(move || {
let bash = handle as *mut Bashkit;
unsafe {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both previous blockers are fixed and verified (ran the branch locally):

  • validate_mount_root now enforces bashkit::is_sensitive_mount_path with the exact-entry consent rule — confirmed my earlier PoC (.ssh under a broad $HOME allowlist) is now rejected, and that it's a genuinely stronger rule than the builder/JS precedent (which only re-checks sensitivity when no allowlist is set at all, as the reply correctly points out). Good catch, and thanks for hoisting is_sensitive_mount_path to a shared pub fn rather than duplicating the list.
  • RealFs::new deprecation is now silenced with #[allow(deprecated)] matching apply_real_mounts's justification. Confirmed cargo clippy -p bashkit-capi -- -D warnings (lib only) is clean.

However, the new cancellation test introduces a fresh clippy failure under --all-targets (the flag the project's own CI/pre-PR checklist actually runs):

error: unnecessary `unsafe` block
   --> crates/bashkit-capi/tests/abi.rs:367:13
    |
336 |     unsafe {
    |     ------ because it's nested under this `unsafe` block
...
367 |             unsafe {
    |             ^^^^^^ unnecessary `unsafe` block

The unsafe { ... } inside the std::thread::spawn(move || { ... }) closure at line 367 is redundant since the closure literal is written inside the outer unsafe block at line 336 and inherits it. Confirmed with cargo clippy -p bashkit-capi --all-targets -- -D warnings. Functionally everything passes (cargo test -p bashkit-capi: 19/19 green, including this test), it's purely the extra unsafe that needs to go.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 894720c — the closure body is lexically covered by the test's unsafe block, so the inner block is gone.

Comment thread .github/workflows/build-native-libs.yml Outdated
@@ -0,0 +1,98 @@
name: Build native libs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This new workflow is out of scope for a "C API host mounts" PR and looks like it should be dropped rather than merged:

  1. It duplicates existing infra. .github/workflows/c-api-binaries.yml already builds the C ABI for the same 5 platforms (using ubuntu-24.04-arm for native aarch64 instead of zig cross-compilation) via scripts/build-c-api.sh, with a rigorous -Werror//WX smoke test against the public header on every platform. This new workflow re-implements a weaker subset of that.
  2. It doesn't follow this repo's action-pinning convention. Every other workflow in .github/workflows/ pins third-party actions to a commit SHA with a version comment (e.g. dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0, actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1). This file uses mutable tags throughout (dtolnay/rust-toolchain@1.95.0, actions/checkout@v4, Swatinem/rust-cache@v2, taiki-e/install-action@v2, mlugg/setup-zig@v2, actions/upload-artifact@v4), which is a real supply-chain regression versus the rest of CI.
  3. No permissions: block, unlike every other workflow here that explicitly scopes down to contents: read.
  4. Per the PR comment thread, this appears to exist to drive the external Bashkit4j Java binding's own release builds rather than anything this repo's C API tests need — cargo test --release -p bashkit-capi in the test job already runs under the existing ci.yml/c-api-binaries.yml setup, so the only new thing this adds is unpinned, unscoped cross-platform artifact building on workflow_dispatch.

Given this repo's "small, incremental PR-sized changes" convention, I'd suggest dropping this file from this PR entirely — it's unrelated to the mount/cancellation feature work and would need its own justification (and SHA-pinning, and a decision on whether upstream wants to host bashkit4j's build pipeline at all) as a separate change.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dropped in 99e0ea7. Agreed on all four points. The workflow still lives on the fork's fork-main-backup branch so Bashkit4j packaging can keep using it ad hoc until it's either proposed upstream properly (SHA-pinned, scoped permissions, own justification) or moved into the binding's own repo.

}
}

/// Requests cancellation of the execution currently running on `bash`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor process note, not a code issue: bashkit_cancel/bashkit_clear_cancel is a second, independent feature (wiring up the already-existing Bash::cancellation_token()/Error::Cancelled to the ABI) bundled into a PR titled/scoped around host-directory mounts. The implementation itself looks correct and is well tested (lock-free AtomicBool outside the state mutex, sticky-until-cleared semantics, good test coverage), so I'm not blocking on it — but per this repo's preference for small, single-purpose PRs, consider splitting cancellation into its own PR next time so mounts and cancellation can be reviewed/merged/reverted independently.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged — keeping cancellation in this PR per your non-blocking note, but future feature work goes up as single-purpose PRs.

The worker closure is lexically nested under the test's unsafe block, so
its own unsafe block triggers clippy's unused_unsafe under --all-targets.
Duplicates c-api-binaries.yml, uses unpinned action tags, and exists to
drive the external Bashkit4j packaging pipeline; it needs its own
justification (SHA pinning, permissions block, upstream-hosting decision).
A copy is preserved on the fork's fork-main-backup branch.
@tersePrompts

Copy link
Copy Markdown
Contributor Author

Second round addressed in 894720c + 99e0ea7:

  • Clippy --all-targets: removed the redundant inner unsafe block in the cancellation worker closure (the outer test unsafe covers it lexically).
  • build-native-libs.yml: dropped from the PR. A copy survives on the fork's fork-main-backup branch for Bashkit4j ad-hoc builds; if upstream ever wants it, it should come back as its own change with SHA-pinned actions and a permissions: block.

Also confirming your non-blocking note on scope: cancellation stays here per your call; follow-up features will be split out. CI note: upstream runs for this fork still need maintainer approval, so we've been verifying each push via a fork-internal scratch PR (tersePrompts#1) running the same pull_request suite — current head is fmt/clippy-clean there, test jobs pending.

@chaliy
chaliy self-requested a review September 5, 2026 17:36
@chaliy
chaliy merged commit 476d31a into everruns:main Sep 5, 2026
37 checks passed
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