Skip to content

pr-steward: recurring tick scheduler for the in-cluster claude-remote device (NON-CCR) #276

Open

Description

@mashkovd

Context

The pr-steward escalation canary passed end-to-end on 2026-05-22 (image 0.2.3, tick 20260522T161147Z): it discovered the canary PR, fell back to REST for check status (App-token GraphQL limitation, fixed in mctl-claude-remote #8), escalated ready-to-merge + one Telegram ping, and did NOT merge. See the closed canary PR #268.

What's missing for real operation: ticks are still fired manually by sending Run the pr-steward skill into the device's live session. We need a recurring scheduler.

The hard constraint (do not regress)

pr-steward runs on the in-cluster claude --remote-control device pod (labs/claude-remote). A tick = the prompt Run the pr-steward skill posted into that device's live session (claude.ai/code/session_…).

A claude.ai CCR RemoteTrigger CANNOT drive this. CCR routines run in ephemeral cloud sandboxes (clone a repo, apt install kubectl) — a separate environment with none of the device's pod mounts (App PEM tmpfs, /etc/steward/*, codex-watch overlay). The device trigger trig_01MAXGkG4S9cw3Jzqrx75wdL has no message events and never auto-fires. So the scheduler must inject the prompt into the device session, not spawn a cloud run.

Candidate mechanisms (pick one — needs design decision first)

  1. In-pod cron / sidecar self-prompt — a process in the pod that posts the prompt into its own remote-control session on a cadence. Keeps everything in-cluster; survives without the operator's Mac. Open question: how to programmatically inject into the active --remote-control session from inside the pod.
  2. Mac LaunchAgent / dispatch — the operator's Mac (already has the dispatch/LaunchAgents path) drives the session via the claude.ai web/API on a schedule. Simpler to build, but depends on the Mac being up and connected.

Acceptance

  • A tick fires automatically on a defined cadence (e.g. every N minutes/hours) without manual prompting.
  • Re-entrancy safe: overlapping fires don't double-process (the steward already takes a durable tick lock in /run/steward; confirm the scheduler respects/relies on it).
  • Idle-safe: a no-op tick (no in-scope PRs) is cheap and leaves no stray state.
  • Observable: each scheduled tick produces a JSON line on the steward-log sidecar channel.

Follow-up (separate, after this lands)

  • Expand pr_filter repo scope beyond the mctlhq/mctl-gitops canary (currently canary-only).

Design notes / routing learning are captured in memory project_pr_steward.md.

Activity

  1. mashkovd commented on May 22, 2026

    @mashkovd
    MemberAuthor

    Stage-2 canary (2026-05-22) reinforced why this matters. The unattended merge worked, but it took ~5 retries — all due to manual remote-control fragility, not pipeline logic:

    1. Config is a subPath mount (/etc/steward/pr-steward.config.json) with no checksum/config annotation on base-service → config changes don't hot-reload, pod restart required.
    2. Each pod restart mints a new claude --remote-control session id — ticking a stale session id silently no-ops (verify via ... logs | grep session_).
    3. One tick hung mid-run when api.mctl.ai/mcp was 502 (Anthropic mcp-proxy layer) + remote-control dropped — the agent loop froze on a Bash status call.

    An in-pod/scheduled tick (this issue) removes the human-driven session-id step and the "tick a stale session" failure mode entirely. If a scheduled tick hangs with infra healthy, that's a steward execution-reliability bug to handle here rather than via manual retries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions