How to Debug Hyprland Window Rule Matching: A Complete Guide

Enable trace logging with HYPRLAND_LOG_LEVEL=trace and monitor the CWindowRuleApplicator logs to see exactly why a rule matches or skips during static and dynamic evaluation cycles.

Hyprland applies window-specific configuration through a sophisticated multi-stage rule system defined in the hyprwm/Hyprland source code. When a window rule fails to affect a target application, the issue typically lies in the selector matching logic, the distinction between static and dynamic evaluation, or the rule's enabled state. This guide traces the execution flow from config parsing in Config::windowRuleMgr() through the CWindowRuleApplicator engine to help you pinpoint why a rule is not taking effect.

Understanding the Window Rule Architecture

Before debugging, you must understand how Hyprland stores and applies rules. The system separates rule definitions from rule execution, using distinct classes for storage and application.

Rule Storage and the Window Rule Manager

All window rules are parsed from your configuration file and stored as CWindowRule objects inside Config::windowRuleMgr(). This manager maintains the master list of rules, but it does not execute them directly. Each rule carries metadata including an enabled flag, selector criteria, and effect definitions that determine when and how the rule triggers.

The Rule Applicator Lifecycle

Every CWindow receives a dedicated CWindowRuleApplicator instance (accessible via m_ruleApplicator) during its construction in src/desktop/view/Window.cpp (lines 108‑146). According to the Hyprland source code, this applicator acts as the evaluation engine, responsible for testing window attributes against stored rules and applying the resulting effects. The applicator persists for the window's lifetime, allowing rules to be re-evaluated when properties change.

Static vs. Dynamic Rule Evaluation

Hyprland evaluates window rules in two distinct phases. Misunderstanding this distinction is a common source of debugging frustration.

Static Rules and Initial Window Creation

Static rules are evaluated once, typically during window creation, and affect immutable properties such as initial workspace assignment, monitor placement, size, or opacity. The evaluation path resides in CWindowRuleApplicator::applyStaticRule() in src/desktop/rule/windowRule/WindowRuleApplicator.cpp (lines 383‑390). If a rule effect is marked as dynamic-only, the static evaluator skips it and emits a TRACE log entry.

Dynamic Rules and Runtime Events

Dynamic rules are evaluated repeatedly on relevant events such as focus changes, title updates, or workspace switches. The method CWindowRuleApplicator::applyDynamicRule() (lines 81‑92) handles this continuous evaluation. Effects like noanim or renderUnfocused require dynamic evaluation because they depend on transient window states rather than initial placement.

How Rule Matching Works Internally

Rule matching relies on selector engines that compare window attributes against rule criteria.

Selector Engines and Match Logic

Each rule contains a selector that tests strings (class, title, workspace name, etc.) using specific match engines. The source code implements several engines:

The selector logic is invoked through CWindow::matchesStaticSelector(), which the CWindowPlacementController::ensurePersistentWorkspacesPresent method calls at line 54 of src/state/WorkspacePlacementController.cpp to resolve workspace assignments.

Where Matches Are Triggered

When a window spawns or updates, the applicator iterates through all stored rules in windowRuleMgr() and calls the appropriate selector. If the selector returns true, the applicator checks whether the effect is valid for the current evaluation context (static or dynamic) before applying it.

Enabling Debug Logging for Rule Matching

Hyprland's built-in logger (Log::logger defined in src/debug/log/Logger.cpp) provides the visibility needed to debug rule evaluation.

Trace Level Logging

Start Hyprland with the environment variable HYPRLAND_LOG_LEVEL=trace (alternatively HF_LOG_LEVEL=trace in some builds) to capture detailed TRACE messages from the rule applicator. At this level, the system logs every rule evaluation attempt, including skipped effects and selector mismatches.

Key Log Messages to Watch

Monitor the log output for specific patterns originating from src/desktop/rule/windowRule/WindowRuleApplicator.cpp:

  • "Skipping effect … not dynamic": Emitted at line 92 when applyDynamicRule() encounters an effect that cannot be applied dynamically, indicating a static/dynamic mismatch.
  • "BUG THIS: WINDOW_RULE_EFFECT_NONE??": An ERR level message at line 114 indicating an unhandled effect type in the rule definition.
  • Selector resolution errors: CWorkspacePlacementController::ensurePersistentWorkspacesPresent logs errors at lines 66‑68 when a rule references a non-existent monitor or workspace that cannot be resolved.

Step-by-Step Debugging Workflow

Follow this systematic approach to identify why a window rule fails to apply:

  1. Verify config parsing: Run hyprctl reload and check logs for "Parsing window rule" messages. If absent, the rule syntax is invalid or the config file is not loading.

  2. Enable trace logging: Launch Hyprland with HYPRLAND_LOG_LEVEL=trace or set the variable before starting the compositor to capture CWindowRuleApplicator activity.

  3. Inspect window state: For a specific window ID, run hyprctl getwindowproperty <winid> ruleApplicator to view active flags such as renderUnfocused, opaque, or dimAround. This confirms which rule properties are currently applied.

  4. Force rule re-evaluation: Execute hyprctl reload or trigger a workspace change (e.g., hyprctl dispatch exec "[workspace 1]") to invoke CWindowRuleApplicator::recheckStaticRules() at line 531, re-evaluating rules without restarting the session.

  5. Isolate selector issues: Create a temporary rule with a unique tag (e.g., windowrulev2 = float, tag:debugtest) and watch for TRACE logs indicating whether the selector matches. If no logs appear, the selector engine is rejecting the syntax.

  6. Check monitor resolution: Ensure rules referencing specific monitors use valid identifiers. The placement controller logs errors when ensurePersistentWorkspacesPresent cannot resolve a monitor name.

Common Pitfalls and Misconfigurations

  • Disabled rules: Each CWindowRule has an enabled flag; disabled rules are silently ignored during evaluation.
  • Missing persistent flag: Workspace rules require m_isPersistent to be set; otherwise, ensurePersistentWorkspacesPresent skips them during placement resolution.
  • Incorrect selector syntax: Tags must use # prefix, regex requires ~, and exact matches use no prefix. Syntax errors cause immediate rejection by the match engine.
  • Static/dynamic effect mismatch: Applying dynamic-only effects (like noanim) during static evaluation results in silent skipping with TRACE logs but no error.

Summary

  • Rule storage: All rules exist as CWindowRule objects in Config::windowRuleMgr().
  • Evaluation engine: Each window owns a CWindowRuleApplicator created in src/desktop/view/Window.cpp that handles static and dynamic rule passes.
  • Debugging method: Use HYPRLAND_LOG_LEVEL=trace to view applyStaticRule() and applyDynamicRule() execution in src/desktop/rule/windowRule/WindowRuleApplicator.cpp.
  • Selector testing: Verify tag (#), regex (~), and other selectors against the unit tests in tests/desktop/rule/matchEngine/.
  • Common fixes: Check the enabled flag, ensure persistent workspace flags are set, and confirm monitor names resolve in WorkspacePlacementController.

Frequently Asked Questions

How do I know if my window rule is being parsed correctly?

Run hyprctl reload and check the Hyprland logs for entries containing "Parsing window rule". If the rule appears in the logs with its selector and effects listed, the config parser accepted it. Absence of this message indicates a syntax error preventing the rule from reaching windowRuleMgr().

Why does my rule work for some windows but not others?

This usually indicates a selector mismatch or a static/dynamic phase error. Check whether the target window's class or title matches your selector exactly using hyprctl clients. If the selector uses regex, verify the pattern in tests/desktop/rule/matchEngine/RegexMatchEngine.cpp. Also confirm the effect is valid for the evaluation phase—some effects only work during dynamic evaluation and are skipped during static application.

How can I force Hyprland to re-evaluate rules without restarting?

Execute hyprctl reload to trigger a full config reload, or change the window's workspace to invoke CWindowRuleApplicator::recheckStaticRules() at line 531 of WindowRuleApplicator.cpp. This method re-evaluates static rules against the current window state without requiring a compositor restart.

What does "BUG THIS: WINDOW_RULE_EFFECT_NONE??" mean in the logs?

This ERR level message appears in src/desktop/rule/windowRule/WindowRuleApplicator.cpp at line 114 when the rule applicator encounters an effect type it does not recognize. This indicates either a corrupted rule object in memory or a rule referencing an effect that exists in the config parser but lacks implementation in the applicator engine. Check your config for typos in effect names and ensure you are running a Hyprland version where the effect is implemented.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →