feat(edaas): add daily headless Renovate PR review timer
Add a systemd user timer on the EDaaS/WSL host that runs Claude Code headless once a day (08:47) to review Renovate dependency PRs awaiting Emma's review. It queries GitHub via the project-scoped github MCP server, excludes PRs against archived repositories, grades each PR's risk, and writes a recommendation-only summary to the journal (journalctl --user -u renovate-review). It never approves or merges. - lyrathorpe/home/renovate-review.nix: wrapper + service + timer. Auth is Vertex AI via the inherited project/region/model env; Claude Code provisions its own network egress, so no proxy is set. The prompt lives in a store file so its literal backticks/$ don't trip shellcheck in the wrapper. - lyrathorpe/home/work.nix: import the module (host-scoped to EDaaS). - system/machine/EDaaS/configuration.nix: enable user linger so the timer fires without an attached login session.
This commit is contained in:
@@ -0,0 +1,96 @@
|
|||||||
|
# Daily automated review of Renovate dependency PRs awaiting Emma's review.
|
||||||
|
#
|
||||||
|
# Host-scoped: imported only from work.nix (the EDaaS/WSL host), so the timer
|
||||||
|
# exists on this machine alone. A systemd *user* timer runs Claude Code headless
|
||||||
|
# once a day; it queries GitHub via the project-scoped github MCP server and
|
||||||
|
# writes a risk-graded summary to the journal (read with
|
||||||
|
# `journalctl --user -u renovate-review`).
|
||||||
|
#
|
||||||
|
# Caveats (the foundation this stands on, none of it owned by this flake):
|
||||||
|
# * Auth is Vertex AI via gcloud Application Default Credentials
|
||||||
|
# (~/.config/gcloud/application_default_credentials.json). When that token
|
||||||
|
# can no longer refresh the run fails; re-auth with `gcloud auth login`.
|
||||||
|
# * The Vertex project, region and model are hardcoded below, copied from the
|
||||||
|
# interactive environment (the corporate launcher injects them; they live in
|
||||||
|
# no config file). If IT changes them, update them here. Claude Code handles
|
||||||
|
# its own network egress, so no proxy is set.
|
||||||
|
# * The github MCP server is defined in ~/code/.mcp.json, so the job runs with
|
||||||
|
# that directory as its working directory.
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
# The review instructions handed to headless Claude. Mirrors the interactive
|
||||||
|
# daily review: queue -> drop archived repos -> grade risk -> recommend.
|
||||||
|
reviewPrompt = ''
|
||||||
|
Daily Renovate PR review for Emma-Thorpe_citrix. Steps:
|
||||||
|
1. github MCP search_pull_requests, query: `is:open is:pr review-requested:Emma-Thorpe_citrix author:app/jenkins-stf-jm` (jenkins-stf-jm[bot] is this org's Renovate bot), perPage 50.
|
||||||
|
2. Build the archived-repo exclusion set: github MCP search_repositories with query `org:csg-citrix-storefront archived:true`, perPage 100, paginate all pages (~128). Collect each archived repo full_name. Do NOT use the `archived:false` qualifier on the PR search itself; it is mis-indexed and returns zero. Filter by the repo set instead.
|
||||||
|
3. Drop any PR whose repository is in the archived set (e.g. csg-citrix-storefront/traefik-fips is archived; a PR to an archived repo cannot merge and is noise).
|
||||||
|
4. For each remaining PR: pull_request_read method=get (diff size, mergeable_state, labels, age) and method=get_status (CI). Read the body's dependency table for what is bumped.
|
||||||
|
5. Assess risk per PR: patch/minor/major; tooling/observability/infra vs application logic; CI state; diff size; staleness (old created_at or stale checks). Flag CVE/security fixes as elevated priority.
|
||||||
|
6. Output a markdown table: PR (linked), repo, change summary, size, CI, risk (Low/Medium/High), verdict (Approve / Hold / Needs rebase). Below it, terse notes for anything needing action. State how many PRs were excluded as archived.
|
||||||
|
7. Do NOT approve or merge anything; recommendation only.
|
||||||
|
If the post-filter search returns zero PRs, say so in one line.
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Hold the prompt in its own store file rather than inline, so its literal
|
||||||
|
# backticks and `$` don't trip shellcheck (SC2016) in the wrapper below.
|
||||||
|
promptFile = pkgs.writeText "renovate-review-prompt.md" reviewPrompt;
|
||||||
|
|
||||||
|
# Tools the headless run is permitted to use without interactive prompts.
|
||||||
|
# Read-only github MCP calls only; nothing that mutates a PR.
|
||||||
|
allowedTools = lib.concatStringsSep "," [
|
||||||
|
"mcp__github-mcp__search_pull_requests"
|
||||||
|
"mcp__github-mcp__search_repositories"
|
||||||
|
"mcp__github-mcp__pull_request_read"
|
||||||
|
];
|
||||||
|
|
||||||
|
renovate-review = pkgs.writeShellApplication {
|
||||||
|
name = "renovate-review";
|
||||||
|
runtimeInputs = [ config.programs.claude-code.package ];
|
||||||
|
text = ''
|
||||||
|
# The github MCP server is project-scoped to ~/code; run from there.
|
||||||
|
cd "$HOME/code"
|
||||||
|
|
||||||
|
# Claude Code auth + endpoint: Vertex AI. These are injected into the
|
||||||
|
# interactive shell by the corporate launcher (not present in any config
|
||||||
|
# file), so a systemd-spawned process must set them explicitly. Do NOT set
|
||||||
|
# HTTP(S)_PROXY: Claude Code self-provisions its own network egress to
|
||||||
|
# Vertex; forcing a proxy here points it at a per-session socket that does
|
||||||
|
# not exist outside an interactive launch and breaks connectivity.
|
||||||
|
export CLAUDE_CODE_USE_VERTEX=1
|
||||||
|
export ANTHROPIC_VERTEX_PROJECT_ID=claude-code-citrix
|
||||||
|
export CLOUD_ML_REGION=global
|
||||||
|
export ANTHROPIC_MODEL='claude-opus-4-8[1m]'
|
||||||
|
|
||||||
|
claude -p "$(cat ${promptFile})" \
|
||||||
|
--allowedTools ${lib.escapeShellArg allowedTools} \
|
||||||
|
--output-format text
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
systemd.user.services.renovate-review = {
|
||||||
|
Unit.Description = "Daily Renovate PR review (headless Claude Code)";
|
||||||
|
Service = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = lib.getExe renovate-review;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.user.timers.renovate-review = {
|
||||||
|
Unit.Description = "Schedule the daily Renovate PR review";
|
||||||
|
Timer = {
|
||||||
|
OnCalendar = "*-*-* 08:47:00";
|
||||||
|
# Run on next boot if the machine was off at the scheduled time.
|
||||||
|
Persistent = true;
|
||||||
|
# Avoid firing exactly on the minute boundary.
|
||||||
|
RandomizedDelaySec = "5m";
|
||||||
|
};
|
||||||
|
Install.WantedBy = [ "timers.target" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,6 +3,11 @@
|
|||||||
{ pkgs, lib, ... }:
|
{ pkgs, lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
# Host-scoped extras for this machine only (the EDaaS/WSL host).
|
||||||
|
imports = [
|
||||||
|
./renovate-review.nix # daily headless Renovate PR review (systemd user timer)
|
||||||
|
];
|
||||||
|
|
||||||
# The work box keeps its own (corporate) ~/.ssh/config; don't let the personal
|
# The work box keeps its own (corporate) ~/.ssh/config; don't let the personal
|
||||||
# programs.ssh (shell.nix) take it over. The ssh-agent below still runs.
|
# programs.ssh (shell.nix) take it over. The ssh-agent below still runs.
|
||||||
programs.ssh.enable = lib.mkForce false;
|
programs.ssh.enable = lib.mkForce false;
|
||||||
|
|||||||
@@ -58,6 +58,13 @@
|
|||||||
systemd.services.docker-desktop-proxy.script = lib.mkForce ''${config.wsl.wslConf.automount.root}/wsl/docker-desktop/docker-desktop-user-distro proxy --docker-desktop-root ${config.wsl.wslConf.automount.root}/wsl/docker-desktop "C:\Program Files\Docker\Docker\resources"'';
|
systemd.services.docker-desktop-proxy.script = lib.mkForce ''${config.wsl.wslConf.automount.root}/wsl/docker-desktop/docker-desktop-user-distro proxy --docker-desktop-root ${config.wsl.wslConf.automount.root}/wsl/docker-desktop "C:\Program Files\Docker\Docker\resources"'';
|
||||||
|
|
||||||
features.swayDesktop.enable = false;
|
features.swayDesktop.enable = false;
|
||||||
|
|
||||||
|
# Keep this user's systemd --user instance running without an open login
|
||||||
|
# session, so the home-manager user timer (renovate-review.nix) fires on
|
||||||
|
# schedule even when no terminal is attached. On WSL the timer still only runs
|
||||||
|
# while the distro itself is up; Persistent=true catches up a missed run at
|
||||||
|
# next start.
|
||||||
|
users.users.emmathorpe.linger = true;
|
||||||
# programs.nix-ld is enabled for all NixOS hosts in common-nixos.nix.
|
# programs.nix-ld is enabled for all NixOS hosts in common-nixos.nix.
|
||||||
# This value determines the NixOS release from which the default
|
# This value determines the NixOS release from which the default
|
||||||
# settings for stateful data, like file locations and database versions
|
# settings for stateful data, like file locations and database versions
|
||||||
|
|||||||
Reference in New Issue
Block a user