feat(scheduler): the CLI's own job log + a safe vault run record (#377) - #379
Merged
Conversation
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tracks 2–3 of v3.4.23. Spec:
01-projects/onebrain/cli/2026-08-03-v3.4.23-design.md. Companion to #378.These primitives have NO production callers yet — deliberately. Task 4 wires them into
run_skill. Landing them separately keeps the pure, heavily-guarded core reviewable on its own; the wiring PR carries its own end-to-end proof. Flagging it here so "dead code" reads as a sequencing decision rather than an oversight.What and why
run_log.rs—open_job_log()opens the CLI's own append-only job log after exec. Unlike the launchd redirect it replaces for skill-mode entries (#378), a real process canmkdir, fall back, and report. It never returns an error: a job that dies because its log could not be opened is exactly the bug this release removes (#372).run_record.rs— pure rendering of one run as vault-readable markdown. No clock, no filesystem, no$HOME— everything injected, so the format is testable on every platform without env mutation. v3.4.22 shipped a check whose tests read the developer's real$HOMEand passed vacuously; splitting the verdict from the resolver is what prevents that.Three design points worth naming:
exit_codeisi32, notOption<i32>—translate_exit(run_skill.rs:407-421) returns128 + signalfor signals and1otherwise, so anOptionwould add a production-unreachable arm.source: Scheduled | Manualexists becauserun_skill::runserves both the scheduler and a human at a terminal. Without it, one manual run would satisfydoctor's staleness check for a job dead a week.safe_tailstrips ANSI and neutralises```runs. A headlessclaude -prun routinely emits fenced blocks, and one would close the record's own fence and corrupt the user's vault note.The plan's own test set had a hole, and coverage found it
The plan specified 3 tests for
run_log. Measuring the coverage HTML showed theOpenOptions::open()Errarm was never executed — the "cannot create" fixture only ever tripscreate_dir_all. A 4th test (suppresses_when_the_open_itself_fails_after_the_directory_exists, forcingEISDIRby occupying the log path with a directory) closes it. BothSuppressedconstructions are now hit.This is the rule earning its keep: sabotage proves a guard can fail on one path; only measuring reach shows which paths it touches at all.
Evidence
Behavioral audit on real data —
safe_tail+renderrun against genuine scheduler output (~/Library/Logs/onebrain/onebrain-daily.stdout, today's 09:01 run, 2618 bytes):The real content contains Thai text, which survived intact — the multibyte-boundary path is proven on genuine data, not only the synthetic fixture. Rendered output is readable Obsidian markdown.
11 tests, all seen to FAIL when their guard is broken (each sabotage applied and restored separately — one sabotage does not vouch for several guards):
create_dir_all→ no-opcreates_a_missing_log_directory….append(true)→.truncate(true)appends_rather_than_truncating…create_dir_allErr →panic!suppresses_instead_of_failing…open()Err →panic!suppresses_when_the_open_itself_fails…(exit 101)renderignoresexit_codea_failure_is_visually_distinct…safe_tailslices from the frontsafe_tail_keeps_the_end…RED and the multibyte test panicked, proving the boundary loop is load-bearingstrip_ansisafe_tail_strips_ansi_escapes.replacesafe_tail_neutralises_a_fence_closerenderignoressourcea_manual_run_is_distinguishable…Coverage per at-risk branch (read line-by-line from the HTML report, not inferred):
Suppressed(create_dir_all) / (open)statusarms ✅ / ❌sourcearms scheduled / manualwhile !is_char_boundarybodyforbodyGates, each bare with its real exit code — never through
tee/tail, which report the pipeline's status and would let a failing gate read as a pass:Both commits verified to build standalone (
cargo build -p onebrain-coreexit 0 at each sha), so a bisect cannot land on a broken tree.Deviations from the plan, stated
run_logtest above.safe_tail_keeps_the_END_not_the_beginning→…the_end…; the plan's literal mixed-case name failsclippy -D warnings(non_snake_case).pub usere-exports added inmod.rs— the plan specified onlypub mod. Task 4 uses the full paths or adds them.