You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browse filesBrowse the repository at this point in the historyBrowse files
Yogthos
committed
plugin: fix 3 critical bugs from R1 audit
From the plugin subsystem audit:
(1) Janet C functions now wrap their bodies in std::panic::catch_unwind.
They were declared `unsafe extern "C-unwind"` which would technically
allow Rust panics to propagate into Janet's C runtime, but Janet
isn't built to clean up after foreign unwinds — heap corruption and
segfaults follow. catch_unwind converts any panic to a safe default
(Janet false for confirm, nil for select).
(2) send_dialog no longer blocks indefinitely on `reply_rx.recv()`.
It now polls every DIALOG_POLL (50 ms) and checks a shared
`SHUTDOWN: Arc<AtomicBool>` thread-local. Worker::Drop flips that
flag before sending Cmd::Shutdown, so an in-flight harness/confirm
or harness/select wakes up and returns None within ~one poll
instead of pinning the worker forever when the UI receiver is
dropped. Before this, a UI exit during a plugin dialog would
cascade into hangs on shutdown.
(3) The init handshake now uses recv_timeout(INIT_TIMEOUT = 10s)
instead of recv(). A worker panic before init_tx.send() would
previously hang main forever; the watchdog bounds that worst
case.
(4) wrap_string asserts via i32::try_from instead of a silent `as i32`
truncation. >2 GB strings now return Janet nil instead of letting
Janet read past the allocation. Real-world dialogs never produce
such strings — this is just defense in depth.
(5) The UI dialog arm no longer eats unrelated user events. Paste,
mouse, scroll, resize, and unrecognized keys during a confirm/
select dialog are now stashed in a `deferred: Vec<UserEvent>`
and re-queued via user_tx.send after the dialog ends. Ctrl+C
inside a dialog is treated as cancel (same as Esc) so the user
can always escape a stuck dialog without dropping queued work.
Tests:
* shutdown_flag_aborts_in_flight_dialog — exercises the cancellation
path: spawn worker, kick off confirm, flip the shutdown flag,
verify the eval returns within 2 s (vs. forever before R1).
* wrap_string_handles_empty — round-trips an empty string through
select to catch wrap_string size-handling regressions.
Total: 504 pass with plugin feature (was 502).
Refs dirge-woq.
Copy file name to clipboardExpand all lines: .beads/issues.jsonl
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -1,3 +1,5 @@
1
+
{"_type":"issue","id":"dirge-woq","title":"R1: fix 3 critical plugin bugs (FFI panic, dialog deadlock, init hang)","description":"From the plugin subsystem audit: (1) wrap JanetCFunctions in catch_unwind so Rust panics don't unwind across the C-FFI boundary into Janet; (2) cancel send_dialog's reply_rx.recv() on worker shutdown so the worker thread doesn't block forever when the UI exits mid-dialog; (3) add timeout to the init handshake so a worker panic before init_tx.send() doesn't hang the main thread. Also: (4) bounds-assert wrap_string's i32 cast for the unlikely \u003e2GB case, (5) make take_string_slot atomic to close the race window, (6) don't eat unrelated user events in the dialog arm.","status":"in_progress","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-05-20T14:59:57Z","created_by":"Yogthos","updated_at":"2026-05-20T15:00:10Z","started_at":"2026-05-20T15:00:10Z","dependency_count":0,"dependent_count":1,"comment_count":0}
2
+
{"_type":"issue","id":"dirge-f5m","title":"R2: add top-5 missing plugin tests + FFI edge cases","description":"From the plugin coverage audit: worker init-failure path, load_file with missing path, store_response round-trip, on-tool-end fires when inner returned Err, concurrent dispatch_tool_hook serialization. Plus FFI edge cases for read_string_arg (keyword/symbol/buffer), read_string_array_arg (empty + tuple-vs-array), wrap_string (empty / multibyte UTF-8). Depends on R1 so the worker init / dialog code is stable.","status":"open","priority":2,"issue_type":"task","owner":"yogthos@gmail.com","created_at":"2026-05-20T14:59:58Z","created_by":"Yogthos","updated_at":"2026-05-20T14:59:58Z","dependencies":[{"issue_id":"dirge-f5m","depends_on_id":"dirge-woq","type":"blocks","created_at":"2026-05-20T11:00:08Z","created_by":"Yogthos","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
1
3
{"_type":"issue","id":"dirge-6ip","title":"Phase 3b: Janet worker thread + harness/confirm + harness/select","description":"Move JanetClient onto a dedicated OS thread so harness/confirm and harness/select can block synchronously from Janet without deadlocking the current_thread tokio UI. Replaces the unsafe impl Send/Sync on PluginManager with a real worker channel. Adds DialogRequest channel the UI loop drains via tokio::select to render confirms and selects. Was originally part of Phase 3 but the refactor is large enough (~2 days) to warrant its own phase.","status":"closed","priority":2,"issue_type":"feature","owner":"yogthos@gmail.com","created_at":"2026-05-20T14:04:20Z","created_by":"Yogthos","updated_at":"2026-05-20T14:43:32Z","started_at":"2026-05-20T14:22:58Z","closed_at":"2026-05-20T14:43:32Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
2
4
{"_type":"issue","id":"dirge-4rl","title":"Phase 4: plugin input transform via on-prompt return","description":"on-prompt hook returning a string can now replace (not just prepend) the user prompt. Backwards-compat: only replace when a new harness flag (harness/replace-prompt) is set. TDD: replace-prompt fires verbatim; default behavior preserved.","status":"closed","priority":2,"issue_type":"feature","owner":"yogthos@gmail.com","created_at":"2026-05-20T13:27:29Z","created_by":"Yogthos","updated_at":"2026-05-20T14:07:28Z","started_at":"2026-05-20T14:04:49Z","closed_at":"2026-05-20T14:07:28Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
3
5
{"_type":"issue","id":"dirge-cp0","title":"Phase 3: UI primitives from Janet (notify/confirm/select)","description":"(harness/notify msg level), (harness/confirm title question), (harness/select title opts). Notify drains into a renderer queue. Confirm/select piggyback the existing AskRequest/QuestionResponse channels. Janet's !Send lock must be released around the oneshot await. TDD: notify queue ordering, confirm/select via mock channel.","status":"closed","priority":2,"issue_type":"feature","owner":"yogthos@gmail.com","created_at":"2026-05-20T13:27:28Z","created_by":"Yogthos","updated_at":"2026-05-20T14:04:43Z","started_at":"2026-05-20T13:55:35Z","closed_at":"2026-05-20T14:04:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
0 commit comments