Skip to content

fix: catch ConfigError in display preview generator#288

Open
ChuckBuilds wants to merge 4 commits intomainfrom
fix/display-preview-config-error
Open

fix: catch ConfigError in display preview generator#288
ChuckBuilds wants to merge 4 commits intomainfrom
fix/display-preview-config-error

Conversation

@ChuckBuilds
Copy link
Owner

@ChuckBuilds ChuckBuilds commented Mar 20, 2026

Summary

  • Adds ConfigError to the exception handler in display_preview_generator() (follow-up to fix: narrow bare except blocks to specific exception types #282)
  • config_manager.load_config() wraps FileNotFoundError, JSONDecodeError, and OSError as ConfigError — without catching it, a corrupt or missing config crashes the display preview SSE endpoint instead of falling back to 128×64 defaults

Test plan

  • Verify display preview endpoint works with valid config
  • Verify display preview endpoint gracefully falls back to 128×64 when config is missing or corrupt

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Display settings: now detect configuration issues and fall back to safe default display dimensions to prevent failures.
  • Bug Fixes / UX
    • Error responses no longer expose raw exception text; users receive standardized, user-friendly messages for various failures.
  • Chores
    • Consolidated logging and removed ad-hoc debug output so errors are logged with details while user-facing messages remain clear.

PR #282 narrowed bare except blocks but missed ConfigError from
config_manager.load_config(), which wraps FileNotFoundError,
JSONDecodeError, and OSError. Without this, a corrupt or missing
config crashes the display preview SSE endpoint instead of falling
back to 128x64 defaults.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

Warning

Rate limit exceeded

@ChuckBuilds has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 36 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 52a7b1c5-c080-4552-bc0e-6c0df92c8623

📥 Commits

Reviewing files that changed from the base of the PR and between bd12459 and 7f95c76.

📒 Files selected for processing (1)
  • web_interface/blueprints/api_v3.py
📝 Walkthrough

Walkthrough

Adds ConfigError handling to display preview dimension parsing, moves WebInterfaceError to fixed, code-based messages via a new _safe_message lookup, and replaces ad-hoc prints with a module-level logger plus standardized generic error responses in many Flask API endpoints.

Changes

Cohort / File(s) Summary
Display preview fallback
web_interface/app.py
Imported ConfigError and added it to the exception tuple caught when computing display width/height in display_preview_generator(), falling back to defaults (128x64).
Web interface error messaging
src/web_interface/errors.py
Added WebInterfaceError._safe_message(error_code) and updated from_exception() to use fixed messages instead of str(exception); changed _infer_error_code() so exception type names containing "Config" map to ErrorCode.CONFIG_LOAD_FAILED.
API logging & error responses
web_interface/blueprints/api_v3.py
Replaced prints and endpoint-local logging with a module-level logger; updated many exception handlers to call logger.exception(...)/logger.error(..., exc_info=True) and return generic "Failed to ..." HTTP responses; added ConfigError import and tightened some control-flow exception handling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title precisely captures the primary change: adding ConfigError handling to the display_preview_generator() function, which is the core fix described in the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/display-preview-config-error

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Remove all traceback.format_exc() from client responses (33 remaining instances)
- Sanitize str(e) from client-facing messages, replacing with generic error messages
- Replace ~65 bare print() calls with structured logger.exception/error/warning/info/debug
- Remove ~35 redundant inline `import traceback` and `import logging` statements
- Convert logging.error/warning calls to use module-level named logger
- Fix WiFi endpoints that created redundant inline logger instances
- Add logger.exception() at all WebInterfaceError.from_exception() call sites
- Fix from_exception() in errors.py to use safe messages instead of raw str(exception)
- Apply consistent [Tag] prefixes to all logger calls for production triage

Only safe, user-input-derived str(e) kept: json.JSONDecodeError handlers (400 responses).
Subprocess template print(stdout) calls preserved (not error logging).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
web_interface/app.py (1)

488-498: Correct fix for the ConfigError gap.

The addition of ConfigError is necessary since it inherits from LEDMatrixError, not from KeyError, TypeError, or ValueError. This properly handles the case where load_config() raises ConfigError for missing/corrupt config files.

Optional: Consider adding a debug log when falling back to defaults to aid remote troubleshooting on Raspberry Pi.

🔧 Suggested logging for debugging
+    import logging
+    logger = logging.getLogger('web_interface')
+    
     # Get display dimensions from config
     try:
         main_config = config_manager.load_config()
         cols = main_config.get('display', {}).get('hardware', {}).get('cols', 64)
         chain_length = main_config.get('display', {}).get('hardware', {}).get('chain_length', 2)
         rows = main_config.get('display', {}).get('hardware', {}).get('rows', 32)
         parallel = main_config.get('display', {}).get('hardware', {}).get('parallel', 1)
         width = cols * chain_length
         height = rows * parallel
-    except (KeyError, TypeError, ValueError, ConfigError):
+    except (KeyError, TypeError, ValueError, ConfigError) as e:
+        logger.debug(f"[Display Preview] Using default dimensions (128x64): {e}")
         width = 128
         height = 64

As per coding guidelines: "Implement comprehensive logging for remote debugging on Raspberry Pi."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web_interface/app.py` around lines 488 - 498, The except block should catch
ConfigError in addition to KeyError/TypeError/ValueError to handle load_config()
failures; update the except clause in the try around
config_manager.load_config() to include ConfigError and ensure ConfigError is
imported from its defining module (where LEDMatrixError/ConfigError are
declared). Also when falling back to defaults for width and height (variables
width, height), add a debug or warning log via the app's logger describing the
fallback and the caught exception to aid remote Raspberry Pi troubleshooting.
Ensure references: config_manager.load_config(), main_config, ConfigError,
width, height.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@web_interface/app.py`:
- Around line 488-498: The except block should catch ConfigError in addition to
KeyError/TypeError/ValueError to handle load_config() failures; update the
except clause in the try around config_manager.load_config() to include
ConfigError and ensure ConfigError is imported from its defining module (where
LEDMatrixError/ConfigError are declared). Also when falling back to defaults for
width and height (variables width, height), add a debug or warning log via the
app's logger describing the fallback and the caught exception to aid remote
Raspberry Pi troubleshooting. Ensure references: config_manager.load_config(),
main_config, ConfigError, width, height.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fed19e22-ef40-483a-84f8-03a143c19044

📥 Commits

Reviewing files that changed from the base of the PR and between f3e7c63 and a0567a6.

📒 Files selected for processing (1)
  • web_interface/app.py

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web_interface/blueprints/api_v3.py (1)

443-469: ⚠️ Potential issue | 🟡 Minor

Catch ConfigError here instead of the wrapped file exceptions.

load_config() now normalizes FileNotFoundError, JSONDecodeError, and OSError into ConfigError, so these three handlers never run. Missing/corrupt config will fall through to the generic branch and return "Unexpected error..." with raw exception text instead of the intended safe message.

💡 Minimal fix
-    except FileNotFoundError as e:
-        logger.error(f"[DIM SCHEDULE] Config file not found: {e}", exc_info=True)
-        return error_response(
-            ErrorCode.CONFIG_LOAD_FAILED,
-            "Configuration file not found",
-            status_code=500
-        )
-    except json.JSONDecodeError as e:
-        logger.error(f"[DIM SCHEDULE] Invalid JSON in config file: {e}", exc_info=True)
-        return error_response(
-            ErrorCode.CONFIG_LOAD_FAILED,
-            "Configuration file contains invalid JSON",
-            status_code=500
-        )
-    except (IOError, OSError) as e:
-        logger.error(f"[DIM SCHEDULE] Error reading config file: {e}", exc_info=True)
-        return error_response(
-            ErrorCode.CONFIG_LOAD_FAILED,
-            f"Error reading configuration file: {str(e)}",
-            status_code=500
-        )
+    except ConfigError as e:
+        logger.warning("[DIM SCHEDULE] Failed to load config: %s", e, exc_info=True)
+        return error_response(
+            ErrorCode.CONFIG_LOAD_FAILED,
+            "Failed to load dim schedule configuration",
+            status_code=500
+        )

Also add from src.exceptions import ConfigError with the other imports.

As per coding guidelines: "Implement graceful degradation to continue operation when non-critical features fail" and "Provide user-friendly error messages that explain what went wrong and potential solutions".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web_interface/blueprints/api_v3.py` around lines 443 - 469, Replace the
multiple specific exception handlers for FileNotFoundError,
json.JSONDecodeError, and (IOError, OSError) with a single except ConfigError
block so errors normalized by load_config() are handled with the intended safe
message; update the import list to include ConfigError from src.exceptions, and
inside the new except ConfigError use logger.error with exc_info=True and return
error_response(ErrorCode.CONFIG_LOAD_FAILED, "Configuration file not found or
invalid", status_code=500) (keeping the existing generic Exception handler for
anything else). Ensure you reference load_config, ConfigError, error_response,
and ErrorCode when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/web_interface/errors.py`:
- Around line 217-218: The current code uses cls._safe_message(error_code) when
error_code may be inferred (e.g., CONFIG_SAVE_FAILED) and thus can produce
misleading "Failed to save configuration" text for config-load failures; update
the logic in the error construction so that if error_code was inferred (or does
not match the exception type) you use a generic, action-agnostic message or
derive the message from the exception type instead of always calling
_safe_message(error_code). Locate the code paths around the callsite that build
the response (referencing _safe_message, _get_exception_details, and the
CONFIG_SAVE_FAILED symbol) and change them to prefer exception-derived messages
or a neutral fallback message when inference is involved, ensuring suggestions
match the actual operation (load vs save).

In `@web_interface/blueprints/api_v3.py`:
- Around line 657-659: save_main_config currently logs full request body and
headers (logger.error(f"DEBUG: save_main_config received data: {data}"),
logger.error(f"DEBUG: Content-Type header: {request.content_type}"),
logger.error(f"DEBUG: Headers: {dict(request.headers)}")), which can leak
secrets; change these to emit only a minimal summary (e.g., top-level keys and
length) at debug level and never log full payloads. Before any validation-error
logging in save_main_config (and the similar locations referenced), redact/mask
sensitive fields in the config and headers (Authorization, Cookie, api_key,
password, secret, token, etc.) and log the masked summary instead; remove direct
dict(request.headers) dumps and content bodies from error logs entirely so only
safe, masked summaries are recorded.

---

Outside diff comments:
In `@web_interface/blueprints/api_v3.py`:
- Around line 443-469: Replace the multiple specific exception handlers for
FileNotFoundError, json.JSONDecodeError, and (IOError, OSError) with a single
except ConfigError block so errors normalized by load_config() are handled with
the intended safe message; update the import list to include ConfigError from
src.exceptions, and inside the new except ConfigError use logger.error with
exc_info=True and return error_response(ErrorCode.CONFIG_LOAD_FAILED,
"Configuration file not found or invalid", status_code=500) (keeping the
existing generic Exception handler for anything else). Ensure you reference
load_config, ConfigError, error_response, and ErrorCode when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a95f4022-837f-4133-b3d6-2b71719a3c51

📥 Commits

Reviewing files that changed from the base of the PR and between a0567a6 and 65d2ff3.

📒 Files selected for processing (2)
  • src/web_interface/errors.py
  • web_interface/blueprints/api_v3.py

Comment on lines +217 to 218
message=cls._safe_message(error_code),
details=cls._get_exception_details(exception),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent incorrect config error messaging when error_code is inferred

At Line 217, the message now depends entirely on inferred error_code. Because config exceptions are currently inferred as CONFIG_SAVE_FAILED, config-load failures can return “Failed to save configuration” with mismatched suggestions, which is misleading for users.

Suggested fix
@@
-        if not error_code:
-            error_code = cls._infer_error_code(exception)
+        if not error_code:
+            error_code = cls._infer_error_code(exception, context)
@@
-    def _infer_error_code(cls, exception: Exception) -> ErrorCode:
+    def _infer_error_code(
+        cls, 
+        exception: Exception, 
+        context: Optional[Dict[str, Any]] = None
+    ) -> ErrorCode:
@@
-        if "Config" in exception_name:
-            return ErrorCode.CONFIG_SAVE_FAILED
+        if "Config" in exception_name:
+            operation = (context or {}).get("operation")
+            if operation == "save":
+                return ErrorCode.CONFIG_SAVE_FAILED
+            if operation == "load":
+                return ErrorCode.CONFIG_LOAD_FAILED
+            return ErrorCode.CONFIG_LOAD_FAILED

As per coding guidelines, “Provide user-friendly error messages that explain what went wrong and potential solutions” and “Make intentions clear through naming and structure (Explicit over Implicit principle)”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web_interface/errors.py` around lines 217 - 218, The current code uses
cls._safe_message(error_code) when error_code may be inferred (e.g.,
CONFIG_SAVE_FAILED) and thus can produce misleading "Failed to save
configuration" text for config-load failures; update the logic in the error
construction so that if error_code was inferred (or does not match the exception
type) you use a generic, action-agnostic message or derive the message from the
exception type instead of always calling _safe_message(error_code). Locate the
code paths around the callsite that build the response (referencing
_safe_message, _get_exception_details, and the CONFIG_SAVE_FAILED symbol) and
change them to prefer exception-derived messages or a neutral fallback message
when inference is involved, ensuring suggestions match the actual operation
(load vs save).

…idate config handlers

- _infer_error_code: map Config* exceptions to CONFIG_LOAD_FAILED
  (ConfigError is only raised by load_config(), so CONFIG_SAVE_FAILED
  produced wrong safe message and wrong suggested_fixes)
- Remove leftover DEBUG logs in save_main_config that dumped full
  request body and all HTTP headers (Authorization, Cookie, etc.)
- Replace dead FileNotFoundError/JSONDecodeError/IOError handlers in
  get_dim_schedule_config with single ConfigError catch (load_config
  already wraps these into ConfigError)
- Remove redundant local `from src.exceptions import ConfigError`
  imports now covered by top-level import
- Strip str(e) from client-facing error messages in dim schedule handler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
web_interface/blueprints/api_v3.py (1)

4576-4577: ⚠️ Potential issue | 🟠 Major

Stop logging raw/parsed plugin config on validation failures.

These lines can leak secrets/PII into logs (request.form values and full parsed config). Log only field keys and a masked config snapshot.

Safer logging pattern
-                    logger.error("[PluginConfig] Raw form data: %s", json.dumps({k: str(v)[:200] for k, v in form_data.items()}, indent=2))
-                    logger.error("[PluginConfig] Parsed config: %s", json.dumps(plugin_config, indent=2, default=str))
+                    logger.error("[PluginConfig] Raw form keys: %s", sorted(form_data.keys()))
+                    masked_config = mask_secret_fields(
+                        plugin_config,
+                        schema.get("properties", {}) if schema else {}
+                    )
+                    logger.error("[PluginConfig] Parsed config (masked): %s", json.dumps(masked_config, indent=2, default=str))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web_interface/blueprints/api_v3.py` around lines 4576 - 4577, The current
error logging in the PluginConfig validation path prints raw form_data and
plugin_config (logger.error calls referencing form_data and plugin_config) which
can leak secrets; change these logger.error calls to only log the form field
keys and a masked/obfuscated snapshot of the config (e.g., replace values with
fixed-length stars or "<redacted>" for any non-empty value) and avoid
serializing full values—use the same variables (form_data, plugin_config) to
derive keys and the masked map before logging so structure is clear but no
secrets/PII are emitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web_interface/blueprints/api_v3.py`:
- Line 2722: In update_plugin, avoid calling logger.exception outside an active
exception and avoid duplicate logs: inside the except block for plugin updates
(where plugin_id and error_msg are available), remove the standalone
logger.exception("[PluginUpdate] Update failed for %s: %s", plugin_id,
error_msg) and consolidate logging to a single call that includes exception
context—either use logger.exception once inside the except or use
logger.error("[PluginUpdate] Update failed for %s: %s", plugin_id, error_msg,
exc_info=True); also remove the subsequent duplicate error/info calls so the
failure is logged only once with the proper exc_info.

---

Duplicate comments:
In `@web_interface/blueprints/api_v3.py`:
- Around line 4576-4577: The current error logging in the PluginConfig
validation path prints raw form_data and plugin_config (logger.error calls
referencing form_data and plugin_config) which can leak secrets; change these
logger.error calls to only log the form field keys and a masked/obfuscated
snapshot of the config (e.g., replace values with fixed-length stars or
"<redacted>" for any non-empty value) and avoid serializing full values—use the
same variables (form_data, plugin_config) to derive keys and the masked map
before logging so structure is clear but no secrets/PII are emitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56c4d8a7-461e-40c2-ad4a-0e6aaee66098

📥 Commits

Reviewing files that changed from the base of the PR and between 65d2ff3 and bd12459.

📒 Files selected for processing (2)
  • src/web_interface/errors.py
  • web_interface/blueprints/api_v3.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/web_interface/errors.py

- update_plugin: change logger.exception to logger.error in non-except
  branch (logger.exception outside an except block logs useless
  "NoneType: None" traceback)
- update_plugin: remove duplicate logger.exception call in except block
  (was logging the same failure twice)
- save_plugin_config validation: stop logging full plugin_config dict
  (can contain API keys, passwords, tokens) and raw form_data values;
  log only keys and validation errors instead

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant