diff --git a/.codex/skills/babysit-pr/SKILL.md b/.codex/skills/babysit-pr/SKILL.md new file mode 100644 index 00000000000..9ae3e615f01 --- /dev/null +++ b/.codex/skills/babysit-pr/SKILL.md @@ -0,0 +1,40 @@ +--- +name: babysit-pr +description: Watch a pull request through checks and review follow-ups without treating readiness as merge intent. +--- + +# Babysit PR + +Use this skill when asked to watch, babysit, or keep an eye on a GitHub pull +request. It is a coordination entry point for the repo's PR workflow metadata, +not an automatic merge command. + +## Workflow + +1. Read `.github/github.json`, especially `prWorkflow`, `importantWorkflows`, + `githubSignals`, and `cleanup`. +2. Identify the PR and branch with `gh pr view --json` and check whether it is + draft, mergeable, blocked, behind, or waiting on review. +3. Watch required checks with `gh pr checks --watch` or, for a known workflow, + the repo's configured wait command. +4. If checks fail, inspect the failing job and report the smallest actionable + fix. Do not mark the PR ready. +5. If checks pass, look for fresh review comments, auto-review findings, and + branch drift. Apply or report genuine findings before declaring readiness. +6. Report readiness separately from merge intent. This repo's metadata says a + ready PR still needs explicit user approval before merging. +7. After an approved merge, verify the post-merge GitHub signals configured in + `.github/github.json` when available, then clean only safe local artifacts. + +## Commands + +Useful commands: + +```sh +gh pr view --json state,isDraft,mergeStateStatus,reviewDecision,url +gh pr checks --watch +gh pr view --comments +``` + +Use `./build-fast.sh` as the local validation gate for code changes in this +repository unless the user explicitly asks for a different check. diff --git a/code-rs/core/src/review_coord.rs b/code-rs/core/src/review_coord.rs index 0de401b6378..b5cd21d48b1 100644 --- a/code-rs/core/src/review_coord.rs +++ b/code-rs/core/src/review_coord.rs @@ -161,6 +161,10 @@ pub fn read_lock_info(scope: Option<&Path>) -> Option { serde_json::from_str(&buf).ok() } +fn lock_file_exists(scope: Option<&Path>) -> bool { + lock_path(scope).map(|path| path.exists()).unwrap_or(false) +} + #[cfg(unix)] fn pid_alive(pid: u32) -> bool { // Safety: kill with signal 0 performs permission/aliveness check only @@ -187,7 +191,15 @@ fn pid_alive(_pid: u32) -> bool { pub fn clear_stale_lock_if_dead(scope: Option<&Path>) -> std::io::Result { let info = match read_lock_info(scope) { Some(i) => i, - None => return Ok(false), + None => { + if lock_file_exists(scope) { + if let Ok(path) = lock_path(scope) { + let _ = fs::remove_file(path); + return Ok(true); + } + } + return Ok(false); + } }; if pid_alive(info.pid) { return Ok(false); @@ -289,6 +301,21 @@ mod tests { drop(guard); } + #[test] + #[serial] + fn malformed_lock_is_cleared_as_stale() { + let dir = TempDir::new().unwrap(); + set_code_home(dir.path()); + let cwd = dir.path(); + let path = lock_path(Some(cwd)).unwrap(); + fs::write(&path, b"not json").unwrap(); + + assert!(clear_stale_lock_if_dead(Some(cwd)).unwrap()); + assert!(!path.exists()); + let guard = try_acquire_lock("after-malformed", cwd).unwrap(); + assert!(guard.is_some()); + } + #[test] #[serial] fn lock_contention_across_components() {