Back to feed

codex/git-remote-and-agent-polish

sledtools/pika · branch #20 · target master · updated 2026-03-20 17:27:17

branch: merged tutorial: ready ci: success

CI

Run #24 · success · 2 lane(s)

head 3962561ed281b9497c6a2a02ae0c8cb6d6e2e63b · queued 2026-03-20 17:25:41

started 2026-03-20 17:27:05

finished 2026-03-20 17:27:08

Lane #60 · check-pika-rust · success · ./scripts/pikaci-staged-linux-remote.sh run pre-merge-pika-rust

lane id pika_rust · retries 0 · queued 2026-03-20 17:25:41

pikaci run 20260320T172707Z-83bcfd47 · target pre-merge-pika-rust

started 2026-03-20 17:27:05

finished 2026-03-20 17:27:08

[pikaci] run started: 20260320T172707Z-83bcfd47 · pre-merge-pika-rust · Run the VM-backed Rust tests from the pre-merge pika lane
[pikaci] run finished: 20260320T172707Z-83bcfd47 · status=skipped · skipped; no changed files matched 13 filter(s)
error (ignored): SQLite database '/var/lib/pika-news/.cache/nix/eval-cache-v6/551324911b3f1ff5bebfe29892221e1b3a3e9ff00f7dba7b4a1a61de17a4dfb9.sqlite' is busy
[pikaci-tools] staged-linux-remote: resolution=nix-build package_root=/nix/store/fcidc2klz0ih3i2vdxh864702gy0paa5-pikaci-0.1.0
[pikaci-tools] staged-linux-remote: pikaci=/nix/store/fcidc2klz0ih3i2vdxh864702gy0paa5-pikaci-0.1.0/bin/pikaci
[pikaci-tools] staged-linux-remote: helper=/nix/store/fcidc2klz0ih3i2vdxh864702gy0paa5-pikaci-0.1.0/bin/pikaci-fulfill-prepared-output
[pikaci-tools] staged-linux-remote: launcher=/nix/store/fcidc2klz0ih3i2vdxh864702gy0paa5-pikaci-0.1.0/bin/pikaci-launch-fulfill-prepared-output
Lane #61 · check-pika-followup · success · ./scripts/pikaci-staged-linux-remote.sh run pre-merge-pika-followup

lane id pika_followup · retries 0 · queued 2026-03-20 17:25:41

pikaci run 20260320T172707Z-72900d8a · target pre-merge-pika-followup

started 2026-03-20 17:27:05

finished 2026-03-20 17:27:08

[pikaci] run started: 20260320T172707Z-72900d8a · pre-merge-pika-followup · Run the VM-backed non-Rust follow-up checks from the pre-merge pika lane
[pikaci] run finished: 20260320T172707Z-72900d8a · status=skipped · skipped; no changed files matched 23 filter(s)
[pikaci-tools] staged-linux-remote: resolution=nix-build package_root=/nix/store/fcidc2klz0ih3i2vdxh864702gy0paa5-pikaci-0.1.0
[pikaci-tools] staged-linux-remote: pikaci=/nix/store/fcidc2klz0ih3i2vdxh864702gy0paa5-pikaci-0.1.0/bin/pikaci
[pikaci-tools] staged-linux-remote: helper=/nix/store/fcidc2klz0ih3i2vdxh864702gy0paa5-pikaci-0.1.0/bin/pikaci-fulfill-prepared-output
[pikaci-tools] staged-linux-remote: launcher=/nix/store/fcidc2klz0ih3i2vdxh864702gy0paa5-pikaci-0.1.0/bin/pikaci-launch-fulfill-prepared-output

merge commit 1e36d41b278ad5b825f9bbcd3150418977ee0e9c

Summary

This branch polishes the Pika forge by migrating the canonical hostname from news.pikachat.org to git.pikachat.org, introducing a non-blocking CI scheduler that launches lane jobs on background threads instead of running them synchronously, hardening mirror-sync concurrency with atomic guards, adding a dedicated git SSH user for forge-native Git access, documenting the forge landing workflow as an agent skill, and laying out a comprehensive product/architecture roadmap in todos/pika-git.md.

Tutorial Steps

Migrate canonical hostname from news.pikachat.org to git.pikachat.org

Intent: Unify all references to the forge under the new git.pikachat.org domain, including the ph client default URL, Nostr auth verify URLs, test fixtures, Caddy virtual host configuration, and documentation.

Affected files: crates/ph/src/lib.rs, crates/pika-news/src/auth.rs, crates/pika-news/templates/base.html, crates/pika-news/README.md, infra/nix/modules/builder.nix, docs/forge-hosted-manual-qa.md

Evidence
@@ -13,7 +13,7 @@ const DEFAULT_BASE_URL: &str = "https://git.pikachat.org";
@@ -1004,7 +1004,7 @@ base_url: "https://git.pikachat.org".to_string(),
@@ -325,7 +325,7 @@ let verify_url = "https://git.pikachat.org/news/auth/verify";
@@ -205,10 +205,11 @@ const verifyUrl = new URL('/news/auth/verify', window.location.origin).toString();
@@ -203,11 +215,16 @@ virtualHosts."git.pikachat.org" = { ... virtualHosts."news.pikachat.org" = { respond "pika forge moved to https://git.pikachat.org" 410 };

The ph CLI client's DEFAULT_BASE_URL changes from https://news.pikachat.org to https://git.pikachat.org, and the corresponding test fixture is updated to match.

In auth.rs, the hardcoded Nostr NIP-98 verify URL in the test switches to the new domain. The browser-side base.html template is also fixed: instead of using window.location.href (which could include query strings or fragment paths), it now constructs the verify URL explicitly via new URL('/news/auth/verify', window.location.origin), making auth work correctly regardless of the page the user is on.

The Caddy reverse-proxy in builder.nix now serves git.pikachat.org as the primary virtual host and returns a 410 Gone response for the old news.pikachat.org hostname.

Documentation in README.md and forge-hosted-manual-qa.md is updated to reference the new canonical Git remote git@git.pikachat.org:pika.git and the web UI at https://git.pikachat.org/news.

Introduce non-blocking CI scheduler with background thread job execution

Intent: Replace the synchronous CI pass model—where the main loop blocks until all claimed lanes finish—with a scheduler that launches each claimed lane job on its own thread and returns immediately, allowing the wake loop to continue draining work.

Affected files: crates/pika-news/src/ci.rs, crates/pika-news/src/branch_store.rs, crates/pika-news/src/web.rs

Evidence
@@ -77,6 +79,23 @@ pub fn schedule_ci_pass_with_updates(
@@ -192,6 +211,56 @@ fn schedule_ci_pass_with_timing_at(
@@ -214,6 +283,52 @@ fn launch_claimed_job(
@@ -232,6 +347,19 @@ fn next_scheduler_claim_limit(
@@ -1670,6 +1670,26 @@ pub fn count_running_ci_lane_runs(
@@ -64,11 +66,13 @@ fn maybe_start_background_ci_pass( ci::schedule_ci_pass_with_updates(

A new schedule_ci_pass_with_updates entry point is added alongside the existing run_ci_pass_with_updates. The key difference is in schedule_ci_pass_with_timing_at: after claiming lane jobs, it calls launch_claimed_job for each one, which spawns a thread::spawn that executes the lane and notifies a tokio::sync::Notify when done. This lets the scheduler return its CiPassResult immediately without waiting for execution to complete.

To compute how many new jobs to claim without tracking active worker threads in-process, a new next_scheduler_claim_limit function queries the database directly. It relies on Store::count_running_ci_lane_runs, a new method that counts rows with status = 'running' across both branch_ci_run_lanes and nightly_run_lanes tables. The claim limit is ci_concurrency - running_count.

In web.rs, the background CI pass switches from run_ci_pass_with_updates to schedule_ci_pass_with_updates, passing a clone of the poll_notify Arc so that finishing threads can wake the main loop for follow-up scheduling.

A comprehensive integration test scheduler_pass_returns_while_running_lane_does_not_block_later_claims validates the core property: a first scheduler pass launches a slow lane, a second pass immediately launches a fast lane without waiting for the slow one, and both eventually succeed.

Harden mirror-sync with atomic concurrency guards and mutation-triggered syncs

Intent: Prevent concurrent mirror pushes, ensure mutations like merge and close trigger an immediate mirror sync, and return HTTP 409 if a manual sync is requested while one is already running.

Affected files: crates/pika-news/src/web.rs

Evidence
@@ -45,6 +45,8 @@ mirror_requested: Arc<AtomicBool>, mirror_running: Arc<AtomicBool>,
@@ -114,6 +118,27 @@ fn run_scheduled_mirror_pass(state: &AppState)
@@ -1995,6 +2024,7 @@ state.mirror_requested.store(true, Ordering::Release);
@@ -3142,10 +3173,27 @@ compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)

Two new AtomicBool fields are added to AppState: mirror_requested signals that a mutation (merge or close) wants an immediate mirror push, and mirror_running acts as a lock to prevent overlapping mirror operations.

run_scheduled_mirror_pass checks mirror_requested and uses compare_exchange on mirror_running to acquire exclusive access. If acquired and a force was requested, it calls the full mirror::run_mirror_pass; otherwise it falls back to the background mirror pass. The flag is cleared on release regardless of outcome.

The merge_handler and close_handler both set mirror_requested to true before waking the poll loop, ensuring the next background tick picks up and pushes to the GitHub mirror immediately.

The admin manual-sync endpoint now also gates on mirror_running with compare_exchange, returning a 409 Conflict if a sync is already in progress. All exit paths in the handler release mirror_running to avoid deadlocks.

Add dedicated git SSH user for forge-native Git access

Intent: Create a system-level `git` user with git-shell as its login shell so that users can push to git@git.pikachat.org:pika.git without needing direct shell access to the builder.

Affected files: infra/nix/modules/pika-news.nix, infra/nix/modules/builder.nix

Evidence
@@ -4,6 +4,7 @@ gitUser = "git";
@@ -83,7 +89,15 @@ users.users."${gitUser}" = { isSystemUser = true; group = serviceGroup; shell = "${pkgs.git}/bin/git-shell"; };
@@ -15,8 +16,13 @@ chown -R ${gitUser}:${serviceGroup} "$repo"
@@ -7,6 +7,18 @@ justinAuthorizedKeys = [ ... ]; benAuthorizedKeys = [ ... ]; paulAuthorizedKeys = [ ... ];
@@ -225,28 +242,22 @@ users.users.git.openssh.authorizedKeys.keys = justinAuthorizedKeys ++ benAuthorizedKeys ++ paulAuthorizedKeys;

In pika-news.nix, a new git system user is created in the same pika-news group, with git-shell as its shell. This restricts the user to Git-only SSH operations. The bare repo bootstrap script now sets ownership to git:pika-news instead of pika-news:pika-news, and explicitly handles .githooks directory permissions so that forge-installed hooks remain owned by the service user and executable.

An additional systemd.tmpfiles rule ensures the state directory permissions are re-applied on boot with z (verify/fix mode).

In builder.nix, SSH authorized keys are refactored into let bindings (justinAuthorizedKeys, benAuthorizedKeys, paulAuthorizedKeys) and the git user receives the union of all three key lists, granting all team members push access through the forge-native SSH path.

Document forge workflow in AGENTS.md and add the land skill

Intent: Ensure agents know that canonical Git is on git.pikachat.org, prefer forge-native workflows over GitHub PRs, and have a detailed skill reference for landing branches.

Affected files: AGENTS.md, .agents/skills/land/SKILL.md, scripts/agent-brief

Evidence
@@ -1,6 +1,12 @@ For this project, canonical Git lives on `git.pikachat.org`.
@@ -17,6 +23,12 @@ ## Forge Workflow
@@ -0,0 +1,139 @@ --- name: land description: Land a branch on the canonical forge ---
@@ -39,6 +39,10 @@ titles+=("ph help") cmds+=("cargo run -q -p ph -- --help")

AGENTS.md gains two new sections. The top-level preamble now states that canonical Git lives on git.pikachat.org and instructs agents to prefer the forge remote and ph for CI/merge/close actions. A new "Forge Workflow" section reinforces the same points with concrete commands.

A new .agents/skills/land/SKILL.md provides a comprehensive agent skill for landing branches. It covers remote policy (inspect before changing), the default landing flow (fetch, rebase, push, ph wait, ph merge), when to use sibling worktrees, ph command reference, failure handling guidance (ignore CodeRabbit/Devin noise, focus on forge CI), and a final-handoff checklist.

The agent-brief script adds ph --help output to the brief so that agents see the forge CLI's capabilities at session start.

Add pika-git roadmap and product direction document

Intent: Capture the living plan for the next iteration of the forge, covering review UI direction, CI UX, anti-stall scheduling properties, operational recovery tooling, architecture boundaries, and phased exploration workstreams.

Affected files: todos/pika-git.md

Evidence
@@ -0,0 +1,352 @@ # Pika Git

A new 352-line roadmap document is added at todos/pika-git.md. It is organized into several major sections:

Product Direction — The forge should optimize for quick branch understanding, merge confidence, and agent/human review loops rather than being a generic GitHub clone.

Review UI Direction — References specific historical commits that had the best review UI features (file sidebar, resizable chat panel, diff presentation) and calls for restoring them while separating CI detail onto a dedicated page.

CI UX Direction — Defines the compact color-coded CI summary for the branch page (green/yellow/red) and what belongs on the dedicated CI details page.

Anti-Stall CI Model — Specifies required scheduler properties: a failed lane must not block unrelated runnable lanes, capacity shortages must be explicit, and target-specific failures must stay scoped. Defines the pika-git vs pikaci responsibility split for scheduling, leases, timeouts, and failure classification.

Operational Recovery Tooling — Lists UI and ph recovery actions (rerun lane, rerun suite, recover stale work, inspect queue reasons) as core requirements for dogfooding.

Architecture Direction — Proposes forge_runtime, forge_service, typed shared models, and domain-split persistence as the target module boundaries.

Phase 1 — Defines five exploration workstreams (review UI recovery, operational recovery, runtime boundaries, pika-git/pikaci boundary, legacy/naming) with explicit questions each must answer before phase 2 implementation begins.

Documentation and config clarifications

Intent: Update README and QA docs to clarify that GitHub repo webhooks are not needed, document the hook_url config field, and note the forge-managed bare-repo hook architecture.

Affected files: crates/pika-news/README.md, docs/forge-hosted-manual-qa.md

Evidence
@@ -30,6 +35,7 @@ - `forge_repo.hook_url`: internal canonical-bare-repo hook target.
@@ -49,6 +55,7 @@ - GitHub repo webhooks are not required for normal forge operation.
@@ -77,6 +77,7 @@ 6. Do not expect any GitHub repo webhook to drive this flow

The pika-news README now documents the forge_repo.hook_url config field, clarifying that it is the internal hook target used by the forge-managed bare-repo Git hooks—not a GitHub webhook endpoint. A new note states explicitly that GitHub repo webhooks are not required for normal forge operation.

The manual QA checklist in forge-hosted-manual-qa.md adds step 6 to the admin/mirror section: testers should not expect a GitHub repo webhook to drive the mirror flow, since canonical ingestion comes from forge-managed bare-repo hooks.

Diff