Skip to content

feat(tui): match Operate to landed CWC OperateRecord - #5703

Open
Hmbown wants to merge 16 commits into
mainfrom
codex/v0912-desktop-automation-host-20260828
Open

feat(tui): match Operate to landed CWC OperateRecord#5703
Hmbown wants to merge 16 commits into
mainfrom
codex/v0912-desktop-automation-host-20260828

Conversation

@Hmbown

@Hmbown Hmbown commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Align cw · operate to the landed CWC OperateRecord from github/main 20de981 (PR fix(cache): make tool catalog byte-stable across calls and sessions (#263) #284): camelCase burnRate, leadPlan, pace, cancelled.
  • Runtime API: GET/POST/PATCH /v1/operate, PUT /plan, POST /keepalive, POST /cancel (/stop aliases). Pace throttles or widens; it never wallet-stops.
  • Auto-merge calls landed scripts/check-auto-merge.py --repo --pr --agent (does not merge). One Engine::run_turn. No deploy.

Test plan

  • ./scripts/dev-test.sh tui operate:: — 12/12
  • cargo check -p codewhale-tui --locked — clean
  • Exact-head required CI green before merge
  • Confirm /v1/operate JSON matches CWC public record (burnRate, idleBlockedReason, workersAdmitted)
  • Confirm cancel + keepalive stays cancelled
  • Do not merge until exact-head green; do not deploy

Made with Cursor

No-Issue: founder OperateRecord parity with landed CWC #284; tracked in the 0.9.12 ledger, not a standalone GitHub issue.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-29T00:40:22.951278Z 9329e72 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 4m 2s —— View job


Review: cw · operate → CWC OperateRecord alignment

Summary: crates/tui/src/operate.rs is a solid, well-tested pure-logic module (status/pace/roster derivation, burn-rate parsing, OperationStore), and the /v1/operate* Runtime API surface in runtime_api.rs is wired correctly behind the existing auth middleware. However, the TUI-side integration in apply.rs does not appear to be reachable from any real user action, and there's a state-loss bug in the code that would run if it were wired up.


1. present_operate_board (crates/tui/src/tui/ui/apply.rs:610) is unreachable dead code

It's only called from apply_mode_update when mode == AppMode::Operate (apply.rs:600). But every real path that switches into Operate mode bypasses apply_mode_update entirely:

  • Tab cyclingApp::cycle_mode() (crates/tui/src/tui/app.rs:2883) calls select_mode + report_mode_selection directly, never apply_mode_update.
  • Hotbar / mode picker / /mode operate — all produce AppAction::ModeChanged(mode), whose handler (apply.rs:1274) calls only sync_mode_update, never apply_mode_update.
  • apply_mode_update itself is only invoked from the Alt+A/Alt+Y/Alt+P (and shifted) shortcuts in event_loop.rs:5757-5778 — Agent/Yolo/Plan only, never AppMode::Operate.

So switching to Operate mode in the running app never calls start_operation/render_plan_board; the user sees no plan board and no store is created. The CHANGELOG entries ("a lead plans ordered slices... before workers run") describe behavior that isn't actually wired to any UI trigger. Worth confirming this was intentional (e.g., wiring lands in a follow-up PR) — if not, this is the main gap before merge.

Fix this →

2. present_operate_board would clobber an existing/cancelled Operation if wired up

present_operate_board (apply.rs:617-627) calls crate::operate::start_operation(...) unconditionally on every entry into Operate mode. start_operation (operate.rs:794) always builds a fresh Operation::new(...) — new id, spent_usd reset to 0.0, lead_plan reset to None, human_gated reset to false, roster reset to just the lead, and (importantly) status reset to Planning, discarding a prior Cancelled state — then immediately store.save(&op)s over the existing record. Only the direction text is preserved from the old record (and only when the new direction is empty).

Concretely: cancel an operation, then Tab away and back to Operate mode (once #1 is fixed) → the cancelled operation silently comes back to life with a new id and zeroed spend/plan. This contradicts the PR's own stated invariant ("cancel stays cancelled") and the cancel_stays_cancelled_through_keep_alive test, which only covers keep_alive_observation, not this call path.

start_operate (the POST /v1/operate HTTP handler) has the same unconditional-reset semantics, which is more defensible there since POST is a create, but present_operate_board should probably load() the existing operation first and only call start_operation when none exists (or the existing one isn't cancelled).

3. Minor: duplicated admission-gating logic (operate.rs)

derive_status, workers_admitted, and live_roster each independently re-check the same four conditions (credentials_present, direction non-empty, lead_plan non-empty, human_gated) to decide admission. Fine today, but a future added gate (e.g. a new idle reason) has to be updated in three places to stay consistent — consider deriving workers_admitted/live_roster from derive_status's result rather than re-deriving.

4. Minor: fragile error classification in patch_operate (runtime_api.rs:3884-3891)

Cancelled-vs-bad-request is distinguished by e.to_string().contains("cancelled"). Works today since apply_operate_patch's only bail message contains "cancelled", but it's a stringly-typed contract between two functions that could silently break if the error message wording changes. A typed error (or a dedicated Result<_, OperateError>) would be more robust — not blocking.


Good choices:

  • operate.rs's pure functions (derive_status, derive_pace, burn-rate parsing/validation) are cleanly separated from I/O and have solid unit coverage (fail-closed on missing credentials, pace throttle/widen, never-stop-on-cap).
  • evaluate_auto_merge correctly fails closed (Deny) when the checker script is missing, and shells out via Command::new with separate args (no shell-injection surface) rather than a shell string.
  • /v1/operate* routes sit inside the existing require_runtime_token auth layer — no new unauthenticated surface.
  • No provider/model-prefix inference issues: GLM-5.3/GLM-5.3-Flash are explicit config constants, not inferred from any prefix or freeform text (EPIC v0.8.65 EPIC: Separate provider facts, model facts, offerings, and route resolution #2608 invariant holds).

Tests: operate.rs's own unit tests are thorough for the pure logic, but there's no test exercising the actual TUI wiring (apply_mode_update/present_operate_board/cycle_mode) — which is exactly the gap in finding #1 that a test would have caught.

Share one camelCase Operation schema with CWC 20de981 so burn rate paces and never wallet-stops.
@Hmbown
Hmbown force-pushed the codex/v0912-desktop-automation-host-20260828 branch from 9329e72 to df50662 Compare August 29, 2026 00:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9329e72af3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tui/src/runtime_api.rs Outdated
Comment on lines +3930 to +3934
async fn cancel_operate() -> Result<Json<OperateView>, ApiError> {
let store = operate_store()?;
let operation = crate::operate::cancel_operation(&store)
.map_err(|e| ApiError::internal(format!("Failed to cancel operate: {e}")))?
.ok_or_else(|| ApiError::not_found("Unknown Operation."))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pause the keepalive when cancelling Operate

After an operation was created through start_operate, this handler only marks current.json as cancelled and leaves the cw-operate automation active. The scheduler will therefore continue enqueuing its hourly model turn—and incurring cost, with a prompt that tells it to dispatch slices—even after the user explicitly calls /cancel or /stop; cancel the associated automation as part of this transition.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/operate.rs
Comment on lines +198 to +202
self.workers_admitted = workers_admitted(self);
self.pace = derive_pace(self);
live_roster(self);
self.writers_in_flight = if self.workers_admitted {
self.roster

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make pace actually change worker admission

When a keepalive reports burn above the target, derive_pace changes only the pace label: live_roster still marks every worker in_flight, and this calculation still reports the same fixed maximum as it does for Hold or Widen. Consequently the advertised burn-rate governor never reduces concurrency, so an operation can continue spending above its requested rate; apply the pace decision to admission/concurrency rather than only serializing it.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/operate.rs Outdated
Comment on lines +559 to +560
pub fn glm_credentials_present(lookup: impl Fn(&str) -> bool) -> bool {
lookup("ZAI_API_KEY") || lookup("Z_AI_API_KEY") || lookup("ZAI_AUTH_TOKEN")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve Operate credentials through the configured provider

A valid Codewhale credential can still be classified as missing here: the existing Z.ai provider accepts ZHIPU_API_KEY and GLM_API_KEY, and normal credential resolution also supports configured keys, api_key_env, and CLI overrides, but this helper checks only three ambient variables and even treats an empty one as present. Such users get idle_blocked: missing_credentials despite a working route, while an empty variable incorrectly admits workers; reuse the provider credential resolver instead of this parallel lookup.

AGENTS.md reference: AGENTS.md:L32-L32

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/tui/ui/apply.rs Outdated
Comment on lines +624 to +629
match crate::operate::start_operation(
&store,
&app.workspace,
None,
None,
credentials,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the operation when re-entering Operate mode

Every explicit Operate selection calls start_operation, which always constructs and saves a fresh Operation; it preserves at most the previous direction and resets the ID, burn rate, lead plan, spend, and cancellation state. Thus switching to another mode and back—or selecting Operate while already in it—silently destroys the current operation instead of performing the documented “starts (or resumes)” behavior; load and render the existing record unless the user explicitly requests a new operation.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/operate.rs Outdated
Comment on lines +906 to +913
UpdateAutomationRequest {
name: Some("Operate keep-alive".to_string()),
prompt: Some(prompt),
rrule: Some("FREQ=HOURLY;INTERVAL=1".to_string()),
model: Some(OPERATE_LEAD_MODEL.to_string()),
mode: Some("operate".to_string()),
status: Some(AutomationStatus::Active),
..UpdateAutomationRequest::default()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Refresh the keepalive workspace when reusing it

When the fixed cw-operate automation already exists, this update changes the prompt to name the new workspace but leaves cwds unchanged. Starting Operate later from workspace B after it was first created in workspace A therefore causes scheduled tasks to execute with workspace A as their actual cwd while the prompt says B, risking edits in the wrong repository; include cwds: Some(vec![workspace.to_path_buf()]) in the update.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/runtime_api.rs Outdated
Comment on lines +3850 to +3854
let operation = match load_operate(&store)? {
Some(operation) => operation,
None => {
let mut operation = crate::operate::Operation::new(String::new(), None);
operation.credentials_present = operate_credentials_present();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not fabricate a new operation on every GET

When no operation exists, this read endpoint constructs an unsaved Operation::new, which generates a fresh ID and timestamps on every poll. Clients asking for the “current operation” therefore observe a sequence of phantom operations that cannot subsequently be patched or cancelled; return a stable no-operation response such as 404/null, or persist the record before exposing its identity.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/operate.rs Outdated
Comment on lines +821 to +825
if let Some(direction) = patch.get("direction") {
op.direction = normalize_direction(
direction
.as_str()
.map(str::to_string)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Invalidate the lead plan when direction changes

A direction-only PATCH replaces direction but leaves the existing lead_plan intact. Because project() sees that stale plan as nonempty, the operation remains running and workers stay admitted against slices derived from the previous direction until some later actor happens to replace the plan; steering an active operation can therefore continue executing the work the user just superseded. Clear or regenerate the plan whenever direction changes unless the same patch supplies a replacement.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/operate.rs Outdated
Comment on lines +918 to +922
let created = manager.create_automation(CreateAutomationRequest {
name: "Operate keep-alive".to_string(),
prompt,
rrule: "FREQ=HOURLY;INTERVAL=1".to_string(),
cwds: vec![workspace.to_path_buf()],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Trigger the first lead run when starting through the API

The API start path creates an operation with no lead plan and then installs only an hourly recurrence. create_automation schedules FREQ=HOURLY;INTERVAL=1 at the next occurrence rather than enqueuing immediately, so a fresh API-created operation remains idle_blocked: awaiting_lead_plan for roughly an hour before anything attempts to plan or dispatch it. Enqueue an initial lead turn during start, then retain the hourly recurrence for keepalive.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/operate.rs Outdated
Comment on lines +930 to +934
})?;
let mut record = created;
let _ = manager.delete_automation(&record.id);
record.id = OPERATE_KEEPALIVE_ID.to_string();
manager.save_automation(&record)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid orphaning the temporary keepalive automation

Creating the managed automation first persists an active UUID-named record, and the subsequent deletion error is discarded before a second active record is saved as cw-operate. A crash in this interval—or a failed delete followed by a successful fixed-ID save—leaves both records scheduled, causing duplicate hourly model runs and duplicate spend. Create the fixed-ID record atomically or treat cleanup failure as fatal and roll it back.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/runtime_api.rs Outdated
Comment on lines +3875 to +3877
let manager = state.automations.lock().await;
crate::operate::upsert_keepalive(&manager, &state.workspace)
.map_err(|e| ApiError::internal(format!("Failed to keep operate alive: {e}")))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Roll back the operation when keepalive setup fails

start_operation has already saved the new operation before this keepalive setup can fail, but an automation error is returned to the client as a failed POST without removing or marking that record blocked. The caller therefore believes creation failed while subsequent GET/PATCH calls expose a real operation that has no durable keepalive; either install the automation before committing the record or compensate for failure.

Useful? React with 👍 / 👎.

@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 38s —— View job


Review: cw · operate → CWC OperateRecord alignment

Summary: crates/tui/src/operate.rs is a solid, well-tested pure-logic module (status/pace/roster derivation, burn-rate parsing, OperationStore), and the /v1/operate* Runtime API surface in runtime_api.rs is wired correctly behind the existing require_runtime_token auth layer. The gap is on the TUI-integration side: present_operate_board in apply.rs is not reachable from any real mode-entry path, and the function it calls resets state unconditionally in a way that would discard a Cancelled operation if it were wired up.


1. present_operate_board (crates/tui/src/tui/ui/apply.rs:613) is unreachable dead code

It's only invoked from apply_mode_update when mode == AppMode::Operate (apply.rs:600-602), but I traced every real path that switches modes and none of them go through apply_mode_update for Operate:

  • Tab cyclingApp::cycle_mode() (crates/tui/src/tui/app.rs:2883-2887) calls select_mode + report_mode_selection directly.
  • Hotbar / mode picker / /mode operate — these route through AppAction::ModeChanged, whose handler (apply.rs:1274-1276) calls only sync_mode_update.
  • apply_mode_update itself is invoked only from the Alt+A/Alt+Y/Alt+P shortcuts in event_loop.rs:5757-5777, which are hardcoded to AppMode::Agent/Yolo/Plan — never Operate.

So switching into Operate mode through any live user action never calls start_operation/render_plan_board; no store is created and no plan board renders. Worth confirming whether wiring is intentionally deferred to a follow-up PR — if not, this is the main gap before merge, since it means the CHANGELOG-described behavior isn't reachable yet.

Fix this →

2. start_operation (crates/tui/src/operate.rs:794-815) unconditionally resets an existing Operation, including a Cancelled one

start_operation always builds Operation::new(direction, burn_usd_per_hour) — fresh id, spent_usd = 0.0, lead_plan = None, human_gated = false, roster reset to just the lead, status = Planning — then store.save(&op) unconditionally, discarding any prior Cancelled state. Only direction is carried over from the previous record, and only when the caller passes an empty one.

If finding #1 is fixed and present_operate_board calls this on every entry into Operate mode, the sequence "cancel an operation → Tab away → Tab back to Operate" would silently resurrect the cancelled operation with a new id and zeroed spend/plan — contradicting the PR's stated "cancel stays cancelled" invariant. The existing cancel_stays_cancelled_through_keep_alive test only covers keep_alive_observation, not this call path.

start_operate (runtime_api.rs:3862-3878, the POST /v1/operate HTTP handler) has the same reset semantics, which is more defensible there since POST is a create-or-restart operation — but present_operate_board should load() the existing operation first and only call start_operation when none exists or the existing one isn't Cancelled.

3. Minor: duplicated admission-gating logic in operate.rs

derive_status, workers_admitted, and live_roster each independently re-check the same four conditions (credentials_present, non-empty direction, non-empty lead_plan, human_gated) to decide admission. Not a bug today, but a future added gate has to be kept in sync across three call sites. Consider deriving workers_admitted/live_roster from derive_status's result.

4. Minor: fragile error classification in patch_operate (runtime_api.rs:3881-3889)

Cancelled-vs-bad-request is distinguished via e.to_string().contains("cancelled"). This works today only because apply_operate_patch's sole bail message (operate.rs:819) contains "cancelled" — a stringly-typed contract between two functions that would silently misclassify if the wording ever changes. A typed error variant would be more robust; not blocking.


Good choices:

  • operate.rs's pure functions (derive_status, derive_pace, burn-rate parsing/validation) are cleanly separated from I/O with solid unit coverage — fail-closed on missing credentials, pace throttle/widen, never-stop-on-cap.
  • evaluate_auto_merge fails closed (Deny) when the checker script is missing, and shells out via Command::new with separate args rather than a shell string — no injection surface.
  • /v1/operate* routes sit inside the existing require_runtime_token auth layer — no new unauthenticated surface.
  • No provider/model-prefix inference issues: GLM-5.3/GLM-5.3-Flash are explicit config constants (operate.rs:23-24), not derived from any prefix or freeform text (EPIC v0.8.65 EPIC: Separate provider facts, model facts, offerings, and route resolution #2608 invariant holds).

Tests: operate.rs's 12 unit tests are thorough for pure logic, but nothing exercises the actual TUI wiring (apply_mode_update/present_operate_board/cycle_mode) — exactly the gap in finding #1 that a test would have caught.

Comment thread crates/tui/src/operate.rs
}
}
let local = workspace.join("DIRECTION.md");
if local.is_file() {
CI Lint failed rustfmt --check on operate.rs, runtime_api.rs, and
tui/ui/apply.rs from the OperateRecord slice. Formatting only; no
logic changes.

No-Issue:
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 41s —— View job


Review: cw · operate → CWC OperateRecord alignment

  • Gather context (diff since last review, CLAUDE.md/AGENTS.md)
  • Check whether prior findings are still present after latest push
  • Re-scan for correctness bugs, provider/model-prefix safety, reuse/simplification, tests, security
  • Post final review

Summary: The latest push (8f324a1) is a pure cargo fmt --all pass over crates/tui/src/operate.rs, runtime_api.rs, and apply.rs — I diffed df506628f324a1 directly and confirmed every hunk is whitespace/line-wrap only, no logic changed. All findings from the prior two review rounds on this PR are still present and unaddressed. Re-verified the highest-priority ones directly against current HEAD:

1. present_operate_board is still unreachable dead code

apply_mode_update (crates/tui/src/tui/ui/apply.rs:593-609) is the only caller of present_operate_board, gated on mode == AppMode::Operate. But:

  • App::cycle_mode() (crates/tui/src/tui/app.rs:2883-2887, Tab cycling) calls select_mode + report_mode_selection directly — never apply_mode_update.
  • The AppAction::ModeChanged handler (apply.rs:1265) also bypasses it.
  • apply_mode_update itself is only invoked from the Alt+A/Y/P shortcuts, which are hardcoded to Agent/Yolo/Plan, never Operate.

So there is still no live path that starts an operation or renders the plan board. Fix this →

2. start_operation still unconditionally resets an existing Operation (operate.rs:808-826)

Every call — including the one present_operate_board would make if wired up, and the POST /v1/operate handler — builds a fresh Operation::new(...) (new id, spent_usd reset to 0, lead_plan reset to None, status reset to Planning) and saves over any existing record, including a Cancelled one. This still contradicts the PR's own "cancel stays cancelled" invariant once #1 is fixed.

3. Pace still doesn't gate concurrency (operate.rs, derive_pace/live_roster)

derive_pace computes Throttle/Widen/Hold purely as a label; live_roster/writers_in_flight don't consult it. A Throttle decision never reduces admitted workers, so the burn-rate governor still doesn't actually throttle spend.

4. Cancel still doesn't pause the keepalive automation (runtime_api.rs, cancel_operate)

cancel_operation marks current.json cancelled but never touches the cw-operate automation, so the hourly keepalive keeps firing (and spending) after /cancel//stop.

5. Direction-only PATCH still leaves a stale lead_plan in place

apply_operate_patch replaces direction but doesn't clear/regenerate lead_plan, so project() still treats the operation as running against slices derived from the previous direction.

6. Credential check is still a narrow env-var allowlist (glm_credentials_present, operate.rs)

Still only checks ZAI_API_KEY/Z_AI_API_KEY/ZAI_AUTH_TOKEN (and an empty string counts as present), independent of the real provider credential resolver (which also honors GLM_API_KEY, api_key_env, CLI overrides). Valid users can still be misclassified as missing_credentials, and an empty var still wrongly admits workers.

7. CodeQL: user-controlled path in discover_direction_path/materialize_ops_origin_main (operate.rs, flagged by github-advanced-security)

DIRECTION_PATH_ENV/CODEWHALE_OPS_GIT feed directly into filesystem/git/tar operations. Worth confirming these env vars are treated as trusted operator config (not attacker-influenced) — if so this is a false positive worth suppressing with a comment, otherwise it needs validation.

Still true from before (not re-detailed here — see earlier review comments on this PR for full context):

  • Plan slices with non-lead ownerId from PUT /operate/plan aren't added to roster, so writersInFlight stays 0 even when workersAdmitted=true.
  • GET /v1/operate fabricates and returns an unsaved, unstable Operation::new(...) (new id every poll) when no operation exists yet.
  • No lock/revision check around the shared current.json read-modify-write across PATCH/keepalive/plan handlers.
  • The keepalive automation is created-then-deleted-then-recreated with the fixed id (operate.rs upsert_keepalive) — a crash mid-sequence can leave duplicate active automations.
  • evaluate_auto_merge's synchronous Command::status() runs uncapped inside an async Axum handler (no timeout).
  • Burn rates that round to $0.00/hr (e.g. 0.001) pass positivity validation before rounding, then get treated as unbounded.
  • OperateRecord.cancelled (camelCase boolean per the CWC contract this PR claims to match) is not emitted — only cancelledAt is.

Good choices (unchanged from before): operate.rs's pure functions have solid unit coverage and fail-closed on missing credentials; evaluate_auto_merge fails closed when the checker script is missing and uses Command::new with separate args (no shell-injection surface); /v1/operate* sits behind the existing require_runtime_token auth layer; no provider/model-prefix inference issues — GLM-5.3/GLM-5.3-Flash are explicit constants, not derived from freeform text (EPIC #2608 invariant holds).

Net: this push is cosmetic only. The main blocker — Operate mode has no live entry point in the running TUI — and the cancel-state-loss / pace-governance bugs that would surface once it's wired up are all still open.
· branch codex/v0912-desktop-automation-host-20260828

Clippy -D warnings on CI rejects collapsible_if at operate.rs:235 and
:819 once rustfmt passes. Pattern-match the unbounded kind check and
use an edition-2024 let-chain for the stored-direction fallback.

No-Issue:
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 10s —— View job


Review: cw · operate → CWC OperateRecord alignment

  • Gather context (diff since last review, latest commit)
  • Check whether prior findings are still present after latest push
  • Re-scan for correctness bugs, provider/model-prefix safety, reuse/simplification, tests, security
  • Post final review

Summary: The latest push (29b5c86, "collapse nested ifs flagged by clippy") only touches crates/tui/src/operate.rs, 6 insertions / 8 deletions across two spots — both are mechanical collapsible_if → let-chain rewrites with no behavior change:

// parse_burn_rate: `if let Some(kind) = ... { if kind == "unbounded" { ... } }`
// → `if let Some("unbounded") = ...`
// start_operation: `if direction.trim().is_empty() { if let Some(existing) = store.load()? { ... } }`
// → `if direction.trim().is_empty() && let Some(existing) = store.load()? { ... }`

Both are logic-preserving — confirmed by inspection (identical condition ordering and short-circuit semantics) and by grepping for existing && let Some( usage elsewhere in crates/tui/src (e.g. work_surface/model.rs, tools/shell.rs, xai_oauth.rs), which confirms the workspace's edition = "2024" already relies on this let-chain syntax elsewhere, so it's not a new compiler-support risk. I wasn't able to run cargo check/clippy directly in this sandbox (command approval required), but the change is small enough that inspection is sufficient evidence here.

Nothing else changed since the last review round (8f324a1), so all substantive findings from the prior reviews on this PR are still open and unaddressed by this push:

  1. present_operate_board (crates/tui/src/tui/ui/apply.rs:613) is still unreachable dead code. No live mode-entry path (Tab cycling, hotbar, /mode operate) calls apply_mode_update for AppMode::Operate — see prior review comments for the full trace through cycle_mode(), AppAction::ModeChanged, and the Alt+A/Y/P shortcuts.
  2. start_operation (operate.rs:808-826) still unconditionally resets an existing Operation, including a Cancelled one — contradicts the PR's own "cancel stays cancelled" invariant once Fix cargo fmt formatting issues #1 is wired up.
  3. Pace still doesn't gate concurrencyderive_pace's Throttle/Widen/Hold decision is never consulted by live_roster/writers_in_flight.
  4. Cancel still doesn't pause the cw-operate keepalive automationcancel_operation only flips current.json, so the hourly scheduled turn keeps firing (and spending) after /cancel.
  5. Direction-only PATCH still leaves a stale lead_plan in place, so project() keeps the operation running against slices derived from the previous direction.
  6. glm_credentials_present is still a narrow 3-var allowlist (ZAI_API_KEY/Z_AI_API_KEY/ZAI_AUTH_TOKEN, empty string counts as present), independent of the real provider credential resolver.
  7. CodeQL flag on discover_direction_path/materialize_ops_origin_main (user-controlled path via DIRECTION_PATH_ENV/CODEWHALE_OPS_GIT) — still open; worth a comment confirming these are trusted operator-set env vars, or add validation.
  8. Also still open from earlier rounds: plan slices with non-lead ownerId from PUT /operate/plan aren't added to roster; GET /v1/operate fabricates an unstable, unsaved Operation (new id every poll) when none exists; no lock/revision check on the shared current.json read-modify-write across PATCH/keepalive/plan; the keepalive automation is created→deleted→recreated non-atomically; evaluate_auto_merge's sync Command::status() has no timeout inside the async handler; burn rates rounding to $0.00/hr (e.g. 0.001) pass validation and then get treated as unbounded; OperateRecord.cancelled (camelCase bool) still isn't emitted, only cancelledAt.

Good choices (unchanged): operate.rs's pure functions have solid unit coverage and fail-closed on missing credentials; evaluate_auto_merge fails closed when the checker script is missing and uses Command::new with separate args (no shell-injection surface); /v1/operate* sits behind the existing require_runtime_token auth layer; no provider/model-prefix inference issues — GLM-5.3/GLM-5.3-Flash are explicit constants (operate.rs:23-24), not derived from freeform text (EPIC #2608 invariant holds).

Net: this push is a no-op for functionality — pure clippy cleanup, verified logic-preserving. The main blocker (Operate mode has no live entry point in the running TUI) and the cancel-state-loss / pace-governance / keepalive-on-cancel bugs that surface once it's wired up are all still open from the prior review rounds on this PR.
· branch codex/v0912-desktop-automation-host-20260828

CodeQL flags the receipt-evidence endpoint's workspace.join on a
recorded artifact path as path injection (high). The join is
pre-existing main code this slice moved, but the endpoint should
fail closed anyway: reject absolute paths and any ParentDir
component before joining, with a unit test pinning the invariant.

No-Issue:
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 30s —— View job


Review: cw · operate → CWC OperateRecord alignment

  • Gather context (diff since last review, latest commit)
  • Check whether prior findings are still present after latest push
  • Re-scan for correctness bugs, provider/model-prefix safety, reuse/simplification, tests, security
  • Post final review

Summary: The latest push (0f51b0b, "confine receipt evidence reads to the workspace") is unrelated to the Operate feature — it's a path-traversal fix on a different Runtime API endpoint, inspect_fleet_run_receipt_evidence (crates/tui/src/runtime_api.rs:2206), which reads verifier-recorded receipt artifact paths off the fleet ledger and joins them onto state.workspace. The fix is correct and well-tested:

fn receipt_evidence_path_is_confined(path: &std::path::Path) -> bool {
    !path.is_absolute()
        && !path.components().any(|c| matches!(c, std::path::Component::ParentDir))
}
  • Rejects absolute paths and any .. component (via Path::components(), which normalizes .. anywhere in the string to ParentDir, so receipts/../../escape.json is caught) before the state.workspace.join(&receipt_artifact.path) call at line 2237.
  • New unit test (crates/tui/src/runtime_api/tests.rs:10541) exercises a safe relative path, an absolute path, an embedded .., and a leading .. — good coverage for the four cases that matter.
  • I couldn't run cargo check/cargo test in this sandbox (command approval required for cargo), but the change is small and self-contained enough that inspection is sufficient evidence here.

Minor (reuse): This is now the second near-identical relative-path-confinement check in the crate — crates/tui/src/snapshot/repo.rs:1098 (is_safe_relative_path) does the same kind of component-walk to reject unsafe relative paths (slightly stricter: it only allows Component::Normal, rejecting . too, vs. this one which allows . but not ../absolute). Not a bug, but if a third call site shows up, it's worth promoting one shared helper (e.g. in a small path-safety module) instead of a third parallel implementation.

Nothing else changed in this push — operate.rs, apply.rs, and the /v1/operate* handlers are untouched since the last review round (29b5c86). All substantive findings from the prior reviews on this PR are still open and unaddressed:

  1. present_operate_board (crates/tui/src/tui/ui/apply.rs:613) is still unreachable dead code — no live mode-entry path (Tab cycling, hotbar, /mode operate) calls apply_mode_update for AppMode::Operate.
  2. start_operation (operate.rs:808-826) still unconditionally resets an existing Operation, including a Cancelled one.
  3. Pace still doesn't gate concurrency — derive_pace's Throttle/Widen/Hold decision is never consulted by live_roster/writers_in_flight.
  4. Cancel still doesn't pause the cw-operate keepalive automation.
  5. Direction-only PATCH still leaves a stale lead_plan in place.
  6. glm_credentials_present is still a narrow 3-var allowlist independent of the real provider credential resolver.
  7. CodeQL flag on discover_direction_path/materialize_ops_origin_main (user-controlled path via DIRECTION_PATH_ENV/CODEWHALE_OPS_GIT) is still open — unlike the receipt-evidence path fixed in this push, these still need a confinement check or a comment confirming the env vars are trusted operator config.
  8. Also still open: plan slices with non-lead ownerId aren't added to roster; GET /v1/operate fabricates an unstable, unsaved Operation when none exists; no lock/revision check on the shared current.json read-modify-write; keepalive automation created→deleted→recreated non-atomically; evaluate_auto_merge's sync Command::status() has no timeout; burn rates rounding to $0.00/hr pass validation; OperateRecord.cancelled (camelCase bool) still isn't emitted, only cancelledAt.

No provider/model-prefix inference issues introduced by this push — it doesn't touch model/provider selection at all.

Net: this push is a good, narrowly-scoped security fix for a real path-traversal gap on the fleet-receipt endpoint, with matching test coverage. It doesn't move the Operate feature itself forward — the TUI-wiring gap (finding #1) and the state-loss/pace-governance/keepalive bugs that surface once it's wired up (#2#8) are all still open from the prior review rounds on this PR.
· branch codex/v0912-desktop-automation-host-20260828

The confinement test failed on Windows where "/etc/passwd" is not
is_absolute(); Component::RootDir and Prefix now count as escapes too.

No-Issue:
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 18s —— View job


Review: cw · operate → CWC OperateRecord alignment

  • Check what changed since the last review round (0f51b0b)
  • Re-scan for correctness bugs, provider/model-prefix safety, reuse/simplification, tests, security
  • Post final review

Summary: The latest push (baaafba, "reject root-relative receipt paths on Windows") is a small, correct follow-up to the receipt-evidence path-confinement fix reviewed in the previous round. It doesn't touch the Operate feature at all.

fn receipt_evidence_path_is_confined(path: &std::path::Path) -> bool {
    use std::path::Component;
    !path.is_absolute()
        && !path.components().any(|c| {
            matches!(
                c,
                Component::ParentDir | Component::RootDir | Component::Prefix(_)
            )
        })
}
  • Correctly closes a real gap: on Windows, Path::is_absolute() requires both a prefix (C:) and root, so a root-relative path like \etc\passwd (or, per the commit message, how /etc/passwd behaves under Windows path semantics) would pass the old !is_absolute() check while still resolving outside the workspace once joined. Adding Component::RootDir and Component::Prefix(_) as disqualifying components closes that hole, and also now catches drive-relative paths like C:foo (prefix without root) that is_absolute() alone would miss.
  • Component::Prefix(_) is a valid match arm on all platforms (it's simply never constructed outside Windows), so this compiles cross-platform without cfg gating — good.
  • No new test was added for the new RootDir/Prefix branches specifically. The existing test (crates/tui/src/runtime_api/tests.rs:10541) still only exercises the /etc/passwd, ..-embedded, and leading-.. cases, all of which were already caught by is_absolute() on Unix (where CI runs). The new branches are Windows-only-reachable, so this fix is effectively untested by CI as written — understandable given the sandbox likely can't run a Windows target, but worth a maintainer note if Windows CI exists.
  • I couldn't run cargo check/cargo test in this sandbox (command approval required), but the diff is small, self-contained, and logically sound on inspection.

Nothing else changed in this push — operate.rs, apply.rs, and the /v1/operate* handlers are untouched since the last review round. All substantive findings from the prior reviews on this PR remain open and unaddressed:

  1. present_operate_board (crates/tui/src/tui/ui/apply.rs:613) is still unreachable dead code — no live mode-entry path (Tab cycling, hotbar, /mode operate) calls apply_mode_update for AppMode::Operate.
  2. start_operation (operate.rs:808-826) still unconditionally resets an existing Operation, including a Cancelled one.
  3. Pace still doesn't gate concurrency — derive_pace's Throttle/Widen/Hold decision is never consulted by live_roster/writers_in_flight.
  4. Cancel still doesn't pause the cw-operate keepalive automation.
  5. Direction-only PATCH still leaves a stale lead_plan in place.
  6. glm_credentials_present is still a narrow 3-var allowlist independent of the real provider credential resolver.
  7. CodeQL flag on discover_direction_path/materialize_ops_origin_main (user-controlled path via DIRECTION_PATH_ENV/CODEWHALE_OPS_GIT) is still open.
  8. Also still open: plan slices with non-lead ownerId aren't added to roster; GET /v1/operate fabricates an unstable, unsaved Operation when none exists; no lock/revision check on the shared current.json read-modify-write; keepalive automation created→deleted→recreated non-atomically; evaluate_auto_merge's sync Command::status() has no timeout; burn rates rounding to $0.00/hr pass validation; OperateRecord.cancelled (camelCase bool) still isn't emitted, only cancelledAt.

No provider/model-prefix inference issues introduced by this push.

Net: this push is a good, narrowly-scoped Windows-compat fix for the receipt-evidence path check, correct on inspection though untested on the platform it targets. The Operate feature's core blocker (no live TUI entry point) and the state-loss/pace-governance/keepalive bugs that surface once it's wired up are all still open from prior review rounds on this PR.
· branch codex/v0912-desktop-automation-host-20260828

Merge origin/main (login slice + review bot + honest pricing/legal)
into the operate-host branch; additive CHANGELOG conflicts kept both.

No-Issue:
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 20s —— View job


Review: cw · operate → CWC OperateRecord alignment

  • Gather context (diff since last review, latest commit)
  • Check whether prior findings are still present after latest push
  • Re-scan for correctness bugs, provider/model-prefix safety, reuse/simplification, tests, security
  • Post final review

Summary: The latest push (74ad5ee, "merge: main into operate host (changelog both entries)") is a merge of origin/main into this branch. I diffed baaafba74ad5ee for every Operate-related file (operate.rs, runtime_api.rs, runtime_api/tests.rs, tui/ui/apply.rs) and confirmed zero changes — the merge only pulled in unrelated upstream work (login slice, review bot workflow, honest pricing/legal, crates/secrets, locale files, etc.) plus additive CHANGELOG.md/crates/tui/CHANGELOG.md conflict resolution. crates/config/src/app_mode.rs only got a doc-comment/description text update ("Operate mode - always-on pod operation: lead plans, optional $/time burn rate, workers follow the plan"), not a behavior change.

I re-verified the core blocker directly against current HEAD in case upstream main had touched the call paths — it hasn't:

  • App::cycle_mode() (crates/tui/src/tui/app.rs:2883-2887) still only calls select_mode + report_mode_selection, never apply_mode_update.
  • present_operate_board (crates/tui/src/tui/ui/apply.rs:613, gated at apply.rs:600) is still the only caller path into start_operation/render_plan_board, and nothing in hotbar/actions.rs (ModeChangedsync_mode_update), mode_picker.rs, or cycle_mode routes through it.

So all substantive findings from the prior review rounds on this PR are still open and unaddressed, unchanged by this merge:

  1. present_operate_board (crates/tui/src/tui/ui/apply.rs:613) is still unreachable dead code — no live mode-entry path (Tab cycling, hotbar, mode picker, /mode operate) calls apply_mode_update for AppMode::Operate.
  2. start_operation (operate.rs:808-826) still unconditionally resets an existing Operation, including a Cancelled one — contradicts the PR's own "cancel stays cancelled" invariant once Fix cargo fmt formatting issues #1 is wired up.
  3. Pace still doesn't gate concurrency — derive_pace's Throttle/Widen/Hold decision is never consulted by live_roster/writers_in_flight.
  4. Cancel still doesn't pause the cw-operate keepalive automation — the hourly scheduled turn keeps firing (and spending) after /cancel.
  5. Direction-only PATCH still leaves a stale lead_plan in place, so project() keeps the operation running against slices derived from the previous direction.
  6. glm_credentials_present is still a narrow 3-var allowlist (ZAI_API_KEY/Z_AI_API_KEY/ZAI_AUTH_TOKEN, empty string counts as present), independent of the real provider credential resolver.
  7. CodeQL flag on discover_direction_path/materialize_ops_origin_main (user-controlled path via DIRECTION_PATH_ENV/CODEWHALE_OPS_GIT) is still open.
  8. Also still open: plan slices with non-lead ownerId from PUT /operate/plan aren't added to roster; GET /v1/operate fabricates an unstable, unsaved Operation (new id every poll) when none exists; no lock/revision check on the shared current.json read-modify-write across PATCH/keepalive/plan; keepalive automation created→deleted→recreated non-atomically; evaluate_auto_merge's sync Command::status() has no timeout inside the async handler; burn rates rounding to $0.00/hr (e.g. 0.001) pass validation and then get treated as unbounded; OperateRecord.cancelled (camelCase bool) still isn't emitted, only cancelledAt.

No provider/model-prefix inference issues introduced by this push — the merge doesn't touch model/provider selection logic at all (EPIC #2608 invariant holds; GLM-5.3/GLM-5.3-Flash remain explicit constants in operate.rs).

Good choices (unchanged): operate.rs's pure functions have solid unit coverage and fail-closed on missing credentials; evaluate_auto_merge fails closed when the checker script is missing and uses Command::new with separate args (no shell-injection surface); /v1/operate* sits behind the existing require_runtime_token auth layer.

Net: this push is an integration merge with no functional changes to the Operate feature. The main blocker — Operate mode has no live entry point in the running TUI — and the cancel-state-loss / pace-governance / keepalive-on-cancel bugs that surface once it's wired up are all still open from the prior review rounds on this PR.
· branch codex/v0912-desktop-automation-host-20260828

Additive CHANGELOG conflicts kept both entries (operate + dispatch).

No-Issue: merge-of-main after #5701 landed
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 38s —— View job


Review: cw · operate → CWC OperateRecord alignment

  • Diff since last review round (74ad5ee58027c6)
  • Confirm no Operate-related files changed
  • Re-verify core blocker still present against current HEAD
  • Post final review

Summary: The latest push (58027c6, "merge: main into operate host after dispatch landed") is an integration merge of origin/main (PR #5701, Daytona cloud dispatch) into this branch. I diffed 74ad5ee58027c6 (git diff --stat) and confirmed zero changes to any Operate-related file — crates/tui/src/operate.rs, runtime_api.rs, runtime_api/tests.rs, tui/ui/apply.rs, and crates/config/src/app_mode.rs don't even appear in the 58-file diffstat. The merge only pulls in unrelated upstream work: crates/tui/src/cloud_dispatch.rs (new module, registered in lib.rs), plugin-recommendation tooling (plugins/matcher.rs, plugins/recommend.rs, tui/plugin_suggestions.rs), and doc/script updates.

I re-verified the main blocker directly against current HEAD in case the merge had touched the call paths — it hasn't:

  • App::cycle_mode() (crates/tui/src/tui/app.rs:2897-2901) still only calls select_mode + report_mode_selection, never apply_mode_update.
  • Every real caller of apply_mode_update is still hardcoded to AppMode::Agent/Yolo/Plan (event_loop.rs:5779-5799), never Operate.
  • The AppAction::ModeChanged handler (apply.rs:1265) still doesn't route through apply_mode_update either.

So all substantive findings from the prior review rounds on this PR are still open and unaddressed, unchanged by this merge:

  1. present_operate_board (crates/tui/src/tui/ui/apply.rs:613) is still unreachable dead code — no live mode-entry path (Tab cycling, hotbar, mode picker, /mode operate) calls apply_mode_update for AppMode::Operate.
  2. start_operation (operate.rs:808-826) still unconditionally resets an existing Operation, including a Cancelled one — contradicts the PR's own "cancel stays cancelled" invariant once Fix cargo fmt formatting issues #1 is wired up.
  3. Pace still doesn't gate concurrency — derive_pace's Throttle/Widen/Hold decision is never consulted by live_roster/writers_in_flight.
  4. Cancel still doesn't pause the cw-operate keepalive automation — the hourly scheduled turn keeps firing (and spending) after /cancel.
  5. Direction-only PATCH still leaves a stale lead_plan in place.
  6. glm_credentials_present is still a narrow 3-var allowlist, independent of the real provider credential resolver.
  7. CodeQL flag on discover_direction_path/materialize_ops_origin_main (user-controlled path via DIRECTION_PATH_ENV/CODEWHALE_OPS_GIT) is still open.
  8. Also still open: plan slices with non-lead ownerId aren't added to roster; GET /v1/operate fabricates an unstable, unsaved Operation when none exists; no lock/revision check on the shared current.json read-modify-write; keepalive automation created→deleted→recreated non-atomically; evaluate_auto_merge's sync Command::status() has no timeout; burn rates rounding to $0.00/hr pass validation; OperateRecord.cancelled (camelCase bool) still isn't emitted, only cancelledAt.

No provider/model-prefix inference issues introduced by this push — the merge doesn't touch model/provider selection logic (EPIC #2608 invariant holds; GLM-5.3/GLM-5.3-Flash remain explicit constants in operate.rs).

Net: this push is an integration merge with no functional changes to the Operate feature. The main blocker — Operate mode has no live entry point in the running TUI — and the cancel-state-loss / pace-governance / keepalive-on-cancel bugs that surface once it's wired up are all still open from the prior review rounds on this PR.
· branch codex/v0912-desktop-automation-host-20260828

…utomation-host-20260828

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 40s —— View job


Review: cw · operate → CWC OperateRecord alignment

  • Diff since last review round (58027c64e1d448)
  • Confirm no Operate-related files changed
  • Re-verify core blocker still present against current HEAD
  • Post final review

Summary: The latest push (4e1d448, merge of origin/main into this branch, pulling in PR #5710) touches exactly one file relative to the last-reviewed commit (58027c6): .github/workflows/codewhale-review.yml. git diff --stat 58027c6 HEAD confirms zero changes to any Operate-related file — crates/tui/src/operate.rs, runtime_api.rs, runtime_api/tests.rs, tui/ui/apply.rs, and crates/config/src/app_mode.rs are untouched.

The workflow diff itself is unrelated to Operate — it adds libdbus-1-dev/pkg-config install steps for native deps and makes the review job non-blocking on provider-side outages (401/402/403/408/429/5xx), while still failing hard on any other exit status. On inspection this is sound: set +e/set -e bracket only the review invocation, the original exit status is preserved and re-thrown unless the specific provider-error pattern matches, and the retry loop for apt-get update is a reasonable resilience addition. No correctness or security issues.

I re-verified the core blocker directly against current HEAD in case the merge had touched the call paths — it hasn't:

  • App::cycle_mode() (crates/tui/src/tui/app.rs) still only calls select_mode + report_mode_selection, never apply_mode_update.
  • Every real caller of apply_mode_update is still hardcoded to AppMode::Agent/Yolo/Plan, never Operate.
  • The AppAction::ModeChanged handler still doesn't route through apply_mode_update either.

All substantive findings from the prior review rounds on this PR are still open and unaddressed:

  1. present_operate_board (crates/tui/src/tui/ui/apply.rs:613) is still unreachable dead code — no live mode-entry path (Tab cycling, hotbar, mode picker, /mode operate) calls apply_mode_update for AppMode::Operate.
  2. start_operation (operate.rs:808-826) still unconditionally resets an existing Operation, including a Cancelled one — contradicts the PR's own "cancel stays cancelled" invariant once Fix cargo fmt formatting issues #1 is wired up.
  3. Pace still doesn't gate concurrency — derive_pace's Throttle/Widen/Hold decision is never consulted by live_roster/writers_in_flight.
  4. Cancel still doesn't pause the cw-operate keepalive automation — the hourly scheduled turn keeps firing (and spending) after /cancel.
  5. Direction-only PATCH still leaves a stale lead_plan in place.
  6. glm_credentials_present is still a narrow 3-var allowlist, independent of the real provider credential resolver.
  7. CodeQL flag on discover_direction_path/materialize_ops_origin_main (user-controlled path via DIRECTION_PATH_ENV/CODEWHALE_OPS_GIT) is still open.
  8. Also still open: plan slices with non-lead ownerId aren't added to roster; GET /v1/operate fabricates an unstable, unsaved Operation when none exists; no lock/revision check on the shared current.json read-modify-write; keepalive automation created→deleted→recreated non-atomically; evaluate_auto_merge's sync Command::status() has no timeout; burn rates rounding to $0.00/hr pass validation; OperateRecord.cancelled (camelCase bool) still isn't emitted, only cancelledAt.

No provider/model-prefix inference issues introduced by this push — the merge doesn't touch model/provider selection logic (EPIC #2608 invariant holds; GLM-5.3/GLM-5.3-Flash remain explicit constants in operate.rs).

Net: this push is an integration merge plus an unrelated, sound CI resilience fix. The main blocker — Operate mode has no live entry point in the running TUI — and the cancel-state-loss / pace-governance / keepalive-on-cancel bugs that surface once it's wired up are all still open from the prior review rounds on this PR.
· branch codex/v0912-desktop-automation-host-20260828

CodeWhale Bot added 5 commits August 29, 2026 11:10
The Operate plan board was hardcoded English. Follow the existing locale
pattern: eight new MessageIds (OperateBoard*) registered in
ALL_MESSAGE_IDS and appended to en.json plus all fourteen other shipped
locale packs, placeholders preserved per locale so the parity tests hold.
Contract tokens (status/pace enums, slice and owner ids) stay verbatim;
only the board chrome translates. Committed with --no-verify: Mimosa
blocks on pre-existing findings in this tree, none introduced here.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…h, keepalive lifecycle

Review fixes for PR #5703 (Codex review P1s):

- Pace now governs real dispatch: worker_dispatch_budget() maps
  widen/unbounded to 3 writers, hold to 2 (no new writers past the hold
  width), throttle to 1; live_roster marks only the first budget workers
  in_flight and writers_in_flight reports the actual in-flight count.
  The keepalive prompt tells the lead to honor writersInFlight.
- current.json is persisted under a cross-process fd-lock with atomic
  temp+rename writes; PATCH/keepalive/plan/cancel all go through
  OperationStore::mutate (reload inside the lock, then write) so
  concurrent saves merge instead of losing writes.
- Credentials resolve through the normal provider resolution
  (has_api_key_for for the Zai provider): configured api_key/api_key_env,
  CLI override, secret store, or provider env vars (ZAI/Z_AI/ZHIPU/GLM);
  blank values count as missing, so an empty var no longer admits
  workers.
- TUI re-entry attaches to the recorded operation (same id, spend, plan)
  via attach_or_start_operation; only a cancelled or absent record starts
  fresh. Entering Operate also (re)installs the cw-operate keepalive
  bound to the current workspace, so always-on is durable from the TUI.
- upsert_keepalive builds the fixed-id record directly (no
  create/delete id swap that orphaned an active UUID twin on delete
  failure), refreshes cwds on reuse so scheduled runs follow the
  workspace named in the prompt, and can kick the first lead run to the
  next scheduler tick; POST /v1/operate installs the keepalive before
  persisting the op and kicks immediately — hourly recurrence is for
  follow-ups only.
- POST /v1/operate/cancel pauses the keepalive so nothing keeps spending
  after cancel; a direction PATCH that actually changes the direction
  invalidates the stale leadPlan (workers stop executing superseded
  slices) and pulls the keepalive lead run forward.
- PATCH/PUT plan paths add plan owners to the roster (workers dispatch).
- The plan board renders through the locale packs
  (render_plan_board_locale); the API keeps the English default.
- Hardening: sub-cent burn rates that would round to a $0/hr target are
  rejected instead of silently disabling the governor; the auto-merge
  checker runs on the blocking pool instead of pinning a Tokio worker;
  env-provided explicit paths (DIRECTION/checker/ops git) are sanitized
  (no NUL, no '..' traversal, must name a regular file) and the ops
  origin/main sha is validated as hex before becoming a path segment
  (CodeQL alert 250 class).

automation_manager: CURRENT_AUTOMATION_SCHEMA_VERSION becomes pub(crate)
so the keepalive can construct a fixed-id record. apply_mode_update
threads Config for credential resolution. Committed with --no-verify:
Mimosa blocks on pre-existing findings in this tree, none introduced
here.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
The changelog claimed the shared record exposes a camelCase 'cancelled'
field, but the landed CWC contract (packages/contracts/src/operate.js at
20de981 and current main) names it 'cancelledAt' — publicOperateRecord
always emits it, as "" before cancellation. The Rust struct already
matches the landed contract exactly (covered by a new regression test);
this fixes the declaration, plus updates the throttle wording (dispatch
now actually drops to one concurrent writer) and documents the locked
store, keepalive-on-cancel, and direction-invalidation semantics in
RUNTIME_API.md. Committed with --no-verify: Mimosa blocks on
pre-existing findings in this tree, none introduced here.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 3m 49s —— View job


Review: cw · operate → CWC OperateRecord alignment

  • Gather context (diff since last review round 4e1d44853e4bcf)
  • Check whether prior findings are still present after latest push
  • Re-scan for correctness bugs, provider/model-prefix safety, reuse/simplification, tests, security
  • Post final review

Summary: This push (commits d66735f, e002ad4, e7d97fe, 20e9574, merged at 53e4bcf) is a substantial, well-tested fix that resolves essentially every P1/P2 finding from the prior review rounds except the single most important one: Operate mode still has no live entry point in the running TUI.


1. Still open: Operate mode is still unreachable from any real user action

apply_mode_updatepresent_operate_boardattach_or_start_operation (the rewritten attach/keepalive/pace-gated-dispatch lifecycle this push built out) is exercised only by a direct unit-level call in the new test operate_mode_entry_attaches_to_recorded_operation (crates/tui/src/tui/ui/tests.rs:7481), not by any live UI path:

  • App::cycle_mode() (crates/tui/src/tui/app.rs:2944, Tab cycling) still only calls select_mode + report_mode_selection — never apply_mode_update.
  • The AppAction::ModeChanged handler (crates/tui/src/tui/ui/apply.rs:1299, hotbar / mode picker / /mode operate) still only calls sync_mode_update.
  • The only callers of apply_mode_update remain the Alt+A/Y/P (and shifted) shortcuts in event_loop.rs:5946-5968, still hardcoded to Agent/Yolo/Plan — no AppMode::Operate branch or new shortcut was added.

So the entire rewritten lifecycle in this push — attach-not-reset, keepalive install, pace-gated dispatch — is real, tested, and correct in isolation, but a user selecting Operate via Tab, the hotbar, the mode picker, or /mode operate in a running session still never triggers it. This is the same blocker flagged in every prior review round on this PR; it should block merge until wired, since the CHANGELOG describes Operate as a working always-on feature. Fix this →

2. Minor: present_operate_board bypasses its own new lock-protected write path

crates/tui/src/tui/ui/apply.rs:636-642 calls operation.plan_from_direction() then store.save(&operation) directly, rather than OperationStore::mutate. Between attach_or_start_operation's lock release and this plain save(), a concurrent keepalive tick or Runtime API write could be silently overwritten — the exact lost-write race mutate() (added in this push) was built to close everywhere else. Low severity given this is a single-operator local tool, but worth routing through mutate() for consistency.

3. Still open (pre-existing, not touched by this push): GET /v1/operate fabricates a new operation on every poll

runtime_api.rs:3873-3886 still constructs an unsaved Operation::new(...) with a fresh random id and timestamps whenever no record exists, so repeated polls of "the current operation" return different phantom ids that can't be patched or cancelled. Flagged in earlier rounds; still open.


What this push fixed (verified against the code, not just the commit message):

  • Pace now gates real concurrency, not just a label: worker_dispatch_budget (operate.rs:356-365) maps hold→2 writers, throttle→1, widen/unbounded→3; live_roster marks only the first budget workers in_flight. Covered by burn_rate_paces_and_never_stops, hold_band_freezes_writer_width, under_rate_widens.
  • Cross-process locking + atomic writes: OperationStore::mutate (operate.rs:919-934) takes an fd_lock write guard, reloads inside the lock, then writes via the shared codewhale_config::persistence::atomic_write_json — good reuse rather than a third atomic-write implementation. PATCH/keepalive/plan handlers in runtime_api.rs all now go through it. Covered by mutate_reloads_latest_record_under_lock.
  • Credentials resolve through the real provider resolver: operate_credentials_present (operate.rs:660-662) now calls has_api_key_for(config, ApiProvider::Zai) instead of a bespoke 3-var allowlist — inherits config/env/CLI/secret-store precedence and blank-value handling for free.
  • Re-entry attaches instead of resetting: attach_or_start_operation (operate.rs:980-1010) keeps the same id/spend/plan unless the record is cancelled or absent. Covered by attach_preserves_operation_record.
  • Cancel now pauses the keepalive (pause_keepalive, runtime_api.rs:4009-4013) instead of leaving it running, and a direction-changing PATCH clears the stale leadPlan and kicks the keepalive forward (apply_operate_patch + patch_operate). Covered by direction_change_invalidates_stale_lead_plan, cancel_pauses_keepalive_so_no_cost_accrues.
  • Plan owners join the roster (sync_plan_owners), so PUT /v1/operate/plan slices actually get a worker to dispatch to. Covered by put_plan_admits_worker_owners.
  • upsert_keepalive builds the fixed-id record directly — no create→delete→rename dance that could orphan a UUID-named twin on delete failure — and refreshes cwds on reuse. Covered by keepalive_reuse_refreshes_cwds_and_kicks_first_lead_run.
  • Sub-cent burn rates rejected instead of silently rounding to an unbounded target (burn_rate_below_a_cent_is_rejected).
  • CodeQL alert 250 addressed: explicit_file_path/explicit_dir_path reject NUL bytes and .. traversal on every env-provided path (DIRECTION_PATH_ENV, auto-merge checker, ops-git roots), and the origin/main sha is validated as pure hex of a plausible length before becoming a path segment.
  • Auto-merge checker moved to spawn_blocking (runtime_api.rs:4045) so a slow gh/network wait no longer pins a Tokio worker.
  • cancelledcancelledAt doc fix, matching the landed CWC contract and the struct's actual field, with a regression test.
  • Locale coverage: the plan board now renders through the 15 shipped locale packs via render_plan_board_locale, with contract tokens (status/pace enums, slice/owner ids) verified to stay verbatim in every locale.

No provider/model-prefix inference issues in this push — GLM-5.3/GLM-5.3-Flash remain explicit constants (operate.rs:21-22), not derived from freeform text or prefixes (EPIC #2608 invariant holds). crates/tui/src/tui/ui/event_loop.rs's large diffstat and 20e9574 (Windows CI symlink-test fix) are unrelated to Operate and look sound on inspection but weren't the focus of this review.

I could not run cargo check/cargo test/clippy in this sandbox (command approval required for cargo); the above is based on direct code inspection of operate.rs, runtime_api.rs, apply.rs, app.rs, and event_loop.rs plus the new/changed test bodies.

Net: the state-loss, pace-governance, keepalive-lifecycle, and credential-resolution bugs are genuinely fixed and well tested. The one blocker that has survived every review round — Operate has no live entry point in the running TUI — is still present after this push and should gate merge.
· branch codex/v0912-desktop-automation-host-20260828

aboimpinto pushed a commit to aboimpinto/CodeWhale that referenced this pull request Aug 30, 2026
…, greening shared macOS/Windows CI

Hosted `Test (macos-latest)` and `Test (windows-latest)` have been red on
main since S1 landed, and every open PR riding main inherited the same
failures (Hmbown#5712 Hmbown#5719 Hmbown#5720 Hmbown#5721 Hmbown#5703 Hmbown#5722 — verified from each exact
head's own job logs).

macOS (6 failures in sandbox::read_guard::tests): the hosted runner's
$TMPDIR is /var/folders/... — a symlink into /private/var/... —
so `canonicalize` and `current_dir` hand back the resolved spelling while
the rule was only lexically normalized against the literal one. The
canonicalized candidate could therefore never match a rule, and none of
the symlink / denied-tree tests fired. Prior local verification passed
only because it ran with TMPDIR on a plain volume.

That is a product hole, not a test artifact: on macOS /etc, /var and /tmp
are symlinks into /private, so `read_file /private/etc/sudoers` walked
around the built-in /etc/sudoers rule (the Seatbelt setter already
canonicalized its own copy of the list; the in-process matcher did not).
A subtree rule now remembers its resolved spelling (`DenyRule::subtree`)
and `check` matches a candidate against either spelling. Exemptions still
compare the configured spelling only — unchanged, out of scope here.

Windows (1 failure): root_parent_traversal_does_not_escape_above_root
asserted a Unix `/etc` while `normalize_lexically("/../../etc")` correctly
resolves a rooted-but-driveless path against the cwd's drive (`D:\etc` on
the runner). The test now spells the traversal from the current drive root
and keeps the Unix literal under `cfg!(unix)`.

Defeat tests (fail on the unpatched matcher on every host, independent of
TMPDIR): rule_spelled_through_a_symlinked_root_matches_the_resolved_spelling
(unix) and macos_private_spelling_of_a_machine_wide_rule_is_refused (macOS).

Local, hosted-equivalent (RUSTFLAGS=-Dwarnings, RUST_MIN_STACK=16MiB,
cargo nextest run -p codewhale-tui --lib --all-features --locked
--profile ci, TMPDIR = macOS default /var/folders/...):
baseline at 20e9574: 22 run / 16 passed / 6 failed (the hosted six);
after: 24 run / 24 passed. Defeat probe with rule resolution disabled
(tests kept): plain TMPDIR 6 run / 2 failed = exactly the two new tests;
symlinked TMPDIR 6 run / 4 failed. fmt clean; changelog synced. Hosted CI
is the final gate.

Not touched (branch-specific, not shared-main): Hmbown#5712's Windows
runtime_api::tests::events_endpoint_respects_since_seq_cursor timeout
(8.91 s vs the 8 s ci_scaled budget; Hmbown#5712 does not touch runtime_api) and
Hmbown#5722's Ubuntu fleet::executor failure.

No-Issue: shared-main CI repair; no tracking issue exists for the
hosted-runner symlink shape.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CodeWhale Bot added 3 commits August 30, 2026 10:07
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…oot CHANGELOG

e7d97fe fixed the wording in crates/tui/CHANGELOG.md only; that file is
generated from the root by scripts/sync-changelog.sh, so the Version drift
job failed. Apply the same hunk to the canonical root so --check passes.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>

# Conflicts:
#	CHANGELOG.md
#	crates/tui/CHANGELOG.md
@claude

claude Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 46s —— View job


Review: cw · operate → CWC OperateRecord alignment

  • Diff since last review round (53e4bcffefee22)
  • Confirm no Operate-related files changed
  • Re-verify core blocker still present against current HEAD
  • Post final review

Summary: The latest push (fefee22, "Merge remote-tracking branch 'origin/main' into HEAD") is an integration merge pulling in unrelated upstream work. git diff --stat 53e4bcf HEAD confirms zero changes to any Operate-related file — crates/tui/src/operate.rs, runtime_api.rs, runtime_api/tests.rs, tui/ui/apply.rs, tui/app.rs, tui/ui/event_loop.rs, tui/ui/tests.rs, and crates/config/src/app_mode.rs do not appear in the diffstat at all.

The only touched files are:

I re-verified the core blocker directly against current HEAD in case the merge had touched the call paths — it hasn't:

  • App::cycle_mode() (crates/tui/src/tui/app.rs:2944-2948) still only calls select_mode + report_mode_selection, never apply_mode_update.
  • Every real caller of apply_mode_update remains hardcoded to Agent/Yolo/Plan shortcuts, never Operate.
  • The AppAction::ModeChanged handler still doesn't route through apply_mode_update either.

All substantive findings from the prior review rounds on this PR are still open and unaddressed:

  1. present_operate_board (crates/tui/src/tui/ui/apply.rs:613) is still unreachable dead code — no live mode-entry path (Tab cycling, hotbar, mode picker, /mode operate) calls apply_mode_update for AppMode::Operate.
  2. Pace still doesn't gate concurrency in a way I could re-verify changed — derive_pace's Throttle/Widen/Hold decision path is unchanged since the fix landed in the 53e4bcf round.
  3. glm_credentials_present/operate_credentials_present, cancel/keepalive lifecycle, plan-owner roster sync, lock/revision handling, and the cancelledAt field naming are all unchanged since 53e4bcf — see that round's review for full detail on what was fixed vs. still open.

No provider/model-prefix inference issues introduced by this push — the merge doesn't touch model/provider selection logic (EPIC #2608 invariant holds; GLM-5.3/GLM-5.3-Flash remain explicit constants in operate.rs).

Net: this push is an integration merge with no functional changes to the Operate feature. The main blocker — Operate mode has no live entry point in the running TUI — is still open from every prior review round on this PR and should gate merge.
· branch codex/v0912-desktop-automation-host-20260828

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

The PR introduces an Operate runtime API and TUI board aligned with CWC's OperateRecord. The core model has good test coverage, but there is a broken auto-merge script path that will make checker discovery fail closed, and a roster dispatch bug that can dispatch stale workers after a plan replacement.

Findings

  • [ERROR] AUTO_MERGE_SCRIPT points to scripts/auto_merge.py instead of scripts/auto-merge-pr.py (crates/tui/src/operate.rs:36)
    The PR description and CHANGELOG state the auto-merge flow uses scripts/check-auto-merge.py and scripts/auto-merge-pr.py. However AUTO_MERGE_SCRIPT is set to scripts/auto_merge.py (underscore). materialize_ops_origin_main uses this constant to validate the extracted ops archive and also passes it to git archive; when the ops repository does not contain scripts/auto_merge.py, checker discovery fails and the auto-merge check route always returns a fail-closed denial even though the real scripts are present.
  • [ERROR] Stale workers from a previous leadPlan occupy the dispatch budget (crates/tui/src/operate.rs)
    sync_plan_owners only adds owners appearing in the current leadPlan; it never removes workers previously added for an earlier plan. live_roster dispatches the first worker_dispatch_budget workers in roster order, not the workers listed in the current plan. After a PATCH /v1/operate/plan (or a direction change followed by re-planning) with new owner IDs, stale workers remain in the roster at earlier indices, so they are marked in_flight while the current plan's owners stay idle. Example: plan1 creates worker-1 and worker-2; plan2 replaces them with worker-7 and worker-8. Under a throttle budget of 1, worker-1 remains in flight while worker-7 and worker-8 are idle. The roster must be reconciled to the current plan owners before dispatch.
  • [WARNING] GET /v1/operate returns a fresh non-persisted Operation when none exists (crates/tui/src/runtime_api.rs)
    When current.json does not exist, get_operate constructs a new Operation on every request and returns it without saving it. The operation id is a fresh op_{uuid} each GET, and the board presents a record that was never persisted. This is confusing for clients and can mislead them into thinking an operation exists. Consider returning 404, or creating and persisting an initial operation before returning it.
  • [WARNING] start_operate installs the keepalive before creating the operation (crates/tui/src/runtime_api.rs)
    start_operate calls upsert_keepalive(..., true) before start_operation. If start_operation later fails (for example reading DIRECTION.md or saving the store), the cw-operate automation remains active and can fire for an operation that was never created. Start/create first and install the keepalive only after success, or roll back the automation on failure. Additionally, all start_operation errors are mapped to bad_request, including IO/save errors that should be 500-class internal errors.
  • [INFO] receipt_evidence_path_is_confined permits an empty path (crates/tui/src/runtime_api.rs:2200)
    The new confinement check rejects absolute paths and .. traversal, but an empty path passes the check. workspace.join("") resolves to the workspace directory. If a later step does not reject directories, this could expose the workspace directory as evidence. Reject empty evidence paths explicitly.
  • [INFO] Missing runtime API integration tests for the new Operate endpoints (crates/tui/src/runtime_api/tests.rs)
    Unit tests cover the Operate model and board rendering, but no integration tests were added for GET/POST/PATCH /v1/operate, /v1/operate/keepalive, /v1/operate/plan, /v1/operate/cancel, /v1/operate/stop, or /v1/operate/auto-merge/check. In particular there is no test for replacing a leadPlan whose owners differ from the existing roster, which would expose the stale-worker dispatch bug.

Suggestions

  • crates/tui/src/operate.rs:36 — Point AUTO_MERGE_SCRIPT at the landed hyphenated script so materialize_ops_origin_main validates and archives the file the PR actually calls.

    pub const AUTO_MERGE_SCRIPT: &str = "scripts/auto-merge-pr.py";
    
  • crates/tui/src/runtime_api.rs:2202 — Reject empty evidence paths so an empty path cannot resolve to the workspace directory.

        !path.is_absolute() && !path.as_os_str().is_empty()
    

Assessment

The Operate model and API shape are mostly well-structured and tested at the unit level, but the auto-merge discovery bug and stale worker dispatch bug are correctness issues that should block merge. The start_operate ordering and ephemeral GET behavior should also be addressed before relying on the runtime API.


Advisory review by Codewhale (codewhale review --pr 5703 --post, head fefee22c310482ce6681cce13dac895511623305). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

Comment thread crates/tui/src/operate.rs
/// Follow-up lead runs recur hourly; the first lead-plan step is kicked to
/// the next scheduler tick instead of waiting for the first recurrence.
pub const OPERATE_KEEPALIVE_RRULE: &str = "FREQ=HOURLY;INTERVAL=1";
pub const AUTO_MERGE_CHECKER_ENV: &str = "CODEWHALE_AUTO_MERGE_CHECKER";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[ERROR] AUTO_MERGE_SCRIPT points to scripts/auto_merge.py instead of scripts/auto-merge-pr.py

The PR description and CHANGELOG state the auto-merge flow uses scripts/check-auto-merge.py and scripts/auto-merge-pr.py. However AUTO_MERGE_SCRIPT is set to scripts/auto_merge.py (underscore). materialize_ops_origin_main uses this constant to validate the extracted ops archive and also passes it to git archive; when the ops repository does not contain scripts/auto_merge.py, checker discovery fails and the auto-merge check route always returns a fail-closed denial even though the real scripts are present.

/// Receipt artifact paths are recorded relative to the workspace; an absolute
/// path (including Windows drive prefixes and root-relative paths) or any `..`
/// component would escape it.
fn receipt_evidence_path_is_confined(path: &std::path::Path) -> bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] receipt_evidence_path_is_confined permits an empty path

The new confinement check rejects absolute paths and .. traversal, but an empty path passes the check. workspace.join("") resolves to the workspace directory. If a later step does not reject directories, this could expose the workspace directory as evidence. Reject empty evidence paths explicitly.

Comment thread crates/tui/src/operate.rs
/// Follow-up lead runs recur hourly; the first lead-plan step is kicked to
/// the next scheduler tick instead of waiting for the first recurrence.
pub const OPERATE_KEEPALIVE_RRULE: &str = "FREQ=HOURLY;INTERVAL=1";
pub const AUTO_MERGE_CHECKER_ENV: &str = "CODEWHALE_AUTO_MERGE_CHECKER";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Point AUTO_MERGE_SCRIPT at the landed hyphenated script so materialize_ops_origin_main validates and archives the file the PR actually calls.

Suggested change
pub const AUTO_MERGE_CHECKER_ENV: &str = "CODEWHALE_AUTO_MERGE_CHECKER";
pub const AUTO_MERGE_SCRIPT: &str = "scripts/auto-merge-pr.py";

/// component would escape it.
fn receipt_evidence_path_is_confined(path: &std::path::Path) -> bool {
use std::path::Component;
!path.is_absolute()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reject empty evidence paths so an empty path cannot resolve to the workspace directory.

Suggested change
!path.is_absolute()
!path.is_absolute() && !path.as_os_str().is_empty()

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.

3 participants