Files
command-center/agent/managed-skills/niri-monitor-flap-refresh-triage/SKILL.md
T

6.3 KiB

name, description
name description
niri-monitor-flap-refresh-triage Diagnose niri/amdgpu monitor failures on blankpad — panel dropping, wrong refresh rate, black/flickering output, letterboxed 1080p on an ultrawide — and fix the refresh without reloading the compositor or logging Frank out.

niri monitor triage: flaps, phantom modes, 60 Hz fallback, stale kernel mode list

Frank's reaction to a broken display is immediate and loud; the fix must never log him out. Box: blankpad (Cezanne APU, card1, s2idle only), compositor niri --session as a user unit.

Step 1 — separate the four diseases (do this before touching anything)

niri msg outputs                          # name, mode, position, full mode list per output
for c in /sys/class/drm/card*-*/status; do echo "$(basename $(dirname $c)) $(cat $c)"; done
systemctl --user show niri.service -p ExecMainStartTimestamp -p NRestarts   # did niri restart?
journalctl --user -b --no-pager | grep -iE 'disconnecting connector|new connector|Page flip commit failed|DMCUB|falling back to preferred|could not be found'
journalctl -k -b --no-pager  | grep -iE 'amdgpu|PM: suspend exit|\*ERROR\*|enabling link|DP_LANE_COUNT_SET|DP_LINK_BW_SET'

Reading it:

  • disconnecting connector: "X" then new connector: X → link-level drop (cable/dock/monitor/driver). Count them: grep -c 'disconnecting connector'.
  • Page flip commit failed … EINVAL bursts + kernel dc_dmub_srv_log_diagnostic_data: DMCUB error right after PM: suspend exit → amdgpu display-engine state broken by s2idle resume. Only a reboot clears it; never reboot without an explicit per-run OK.
  • configured mode M could not be found, falling back to preferred → config asks for a mode the panel does not offer; the panel silently runs at EDID-preferred (usually 60 Hz). This is the one you can fix instantly.
  • Config mtimes older than the symptom + no niri restart ⇒ the config did not cause it. Say so explicitly; agents get blamed for display problems they didn't cause.

Step 2 — phantom-mode trap AND stale-mode-list trap (niri fails soft, so both look like hardware)

Two different lies, told by two different layers. output { mode "…" } with an unavailable refresh does not error: niri logs a WARN and drops to preferred on every connect → janky 60 Hz ultrawide, blamed on the monitor. Check the requested mode against the panel's real list:

niri msg outputs | sed -n '/<monitor name>/,/^$/p' | grep -E '^\s+[0-9]+'

If the kernel list lacks modes the EDID advertises (e.g. 3440x1440 absent, largest mode 1080p), discriminate parser gap vs stale cache — compare the kernel's EDID blob with sysfs and decode it:

command -v edid-decode    # parse the live EDID: edid-decode /sys/class/drm/card1-DP-1/edid
modetest -M amdgpu -c 2>/dev/null | awk '/connected[[:space:]]+DP-1/,0' \
  | sed -n '/value:/,/DPMS/p'        # the 384-byte blob DRM actually holds
  • Blob good (all extension blocks present, native DTD visible via edid-decode) and mode list bad → stale mode list cached from a corrupted AUX/DDC read during a flapped hotplug. DRM rebuilds modes only on a clean re-connect, and every subsequent flap re-caches garbage. Software cannot force the rebuild — the fix is a monitor power cycle (unplug power ~30 s; needs Frank's hands), then re-apply the mode. Do NOT conclude "kernel can't parse this EDID" and do not edit config for it.
  • Blob itself degraded → AUX hardware read failure, same physical remedy, worse prognosis (cable/port).

Step 3 — apply the mode WITHOUT a config reload (the no-logout path)

niri msg output "<EDID output name from niri msg outputs>" mode "WxH@refresh"
niri msg outputs | grep -A1 '<monitor>'      # confirm the new mode
pgrep -c niri                                # compositor still alive, same PID

niri msg output … is documented as temporary ("not saved into the config file") — it changes one output and cannot log the session out. rc=0 proves nothing: for a mode outside the kernel's published list niri exits 0 and silently no-ops — always re-read niri msg outputs to confirm. Avoid niri action reload-config when Frank is working: it reloads his whole config.

Step 3.5 — flap census beyond the display: who else dies at the same second

Correlate DP flap timestamps against journalctl -k | grep -E 'usb .*: (new |disconnect)|Synchronize Cache'. A USB hub chain re-enumerating in lockstep with a DP flap means shared power/ground on the monitor or dock — and it identifies what else brown-outs (keyboard, webcam, USB SSD). KT USB Audio (idVendor 12d1 KTMicro) in the tree = the monitor's built-in DAC, i.e. the monitor's USB upstream port is in that hub chain. Kernel enabling link 2 failed: 15 across multiple boots + core_link_write_dpcd (DP_LANE_COUNT_SET/DP_LINK_BW_SET) failed = physical layer (cable/dock/monitor PSU), not compositor, not config: prescribe cable swap / other input after a clean-boot power cycle, and say plainly which of the four diseases you did NOT fix.

Step 4 — persist, and check you edited the file that is actually live

Live niri config is a symlink chain: ~/.config/niri/config.kdl → cfg/display.kdl → cfg/desk-<profile>.kdl, and cfg/desk-*.kdl are symlinks into the repo:

ls -l ~/.config/niri/cfg/display.kdl          # -> desk-1-home.kdl
grep -n '239\|180' ~/.config/niri/cfg/display.kdl   # prove YOUR edit is in the live chain
niri validate                                  # parses the live config; touches nothing

dotfiles/ is on the live-sync.sh heartbeat allowlist, so an edited tracked dotfile stays dirty until the hourly heartbeat commits it — that is the designed state, do not commit it on main yourself.

Desk config law (its own header): panels keyed by EDID identity, not connector name — but connector-keyed leftovers (DP-1, DP-2) survive in desk-1-home.kdl, and a reconnect that comes back without an EDID string hits those instead. Align them to the same mode, or a flap silently changes your refresh rate.

Escalate honestly

Reboot clears DMCUB/EINVAL state → schedule it, don't take it. Persistent drops after a reboot → cable / dock / monitor DP-version setting, i.e. hardware, not config. Report which of the four diseases you found and which you did not fix.