Skip to content

pr_reviewer.repos (+ shadow_mode) not hot-reloaded — Dispatcher caches allowlist at boot #11

Description

@mabry1985

Symptom

Changing `pr_reviewer.repos` (or `shadow_mode`) via `POST /api/settings` persists to config and the graph reloads (`"config saved"`, `"reloaded"`), but the running dispatcher keeps its boot-time values. A manual dispatch to a newly-added repo returned `drop:unlisted-repo` until the container was restarted; after restart the same dispatch ran.

Cause

`Dispatcher.init` snapshots config into instance state:
```python
self.repos = [str(r) for r in (self.cfg.get("repos") or []) if r]
self.shadow = bool(self.cfg.get("shadow_mode", True))
self.promotion_owner = bool(self.cfg.get("promotion_owner", False))
```
The plugin's config-reload path doesn't rebuild the Dispatcher (or re-read these), so live edits are silent no-ops until a process restart. This is the "hot-reload must thread every store" class.

Impact

These read as ordinary settings (no `restart_required` signal — the core schema can't know a plugin caches them), so an operator flipping `shadow_mode` or editing `repos` believes it took effect when it didn't. For a gate flip that's a real footgun (thinking you're formal-blocking a repo you aren't).

Fix options

  1. Re-read `cfg` on each event (read `self.cfg["repos"]` live rather than snapshotting), or
  2. Rebuild the Dispatcher in the plugin's reload hook when `pr_reviewer.*` changes, or
  3. Minimum: register these keys as restart-required so the API surfaces it.

Found flipping Vera to the formal gate (ADR 0078 stage-2), 2026-07-13.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions