Repository navigation
Fixed some Critical Errors - #80
Merged
Merged
Conversation
G4PLS
commented
Apr 26, 2024
Member
- When using main store branch plugins were not able to register at all because version checks were only done on the min_app_version
- Instead of setting min_app_version to None it will now be set to 0.0.0, this does basically the same as None but instead of exiting the function it will return True, which is expected behaviour when no min_app_version is provided
- Added an extra security layer when trying to get the local sha in the store backend. It threw an error when id was not present because None values are not allowed, not the path will just point to icons/ and nothing happens
- Added a check for symlinks when uninstalling a Plugin, only usefull in development and shouldnt do really do anything
…Icons and Wallpapers
… in version checks
…ll now be checked before trying to remove it
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 11, 2026
…aches logs.log (StreamController#80) Install sys.excepthook / threading.excepthook / sys.unraisablehook (src/backend/log_hooks.py) and route them through loguru: main-thread, GLib/Gio-callback (PyGObject dispatches via PyErr_Print -> sys.excepthook, verified headless against a live GLib.MainLoop), plain-thread and GC-time exceptions now land in logs/logs.log, stderr and the About-dialog ring with full tracebacks. KeyboardInterrupt passes through to the previous hook; installation is idempotent; a failure inside the hook itself falls back to sys.__stderr__ and can never recurse. The plugin-dispatch asyncio loop gets the same handler for un-retrieved task exceptions, and dispatch() attaches a done-callback so a failure in _dispatch_batch itself can no longer vanish into a dropped Future -- the one surface no process-global hook can see (pool-task exceptions never reach threading.excepthook). config_logger() moves above the dbus probe / deck reset / migrations (that pre-sink startup phase used to be stderr-only) but stays below the CLI early-returns, so short-lived helper invocations never open (or rotate) the running app's log files. faulthandler is re-pointed from stderr to logs/faulthandler.log once gl.DATA_PATH is final (it can come from --data or the static settings file, so not resolvable at import time), append-mode with boot markers so a crash dump survives the restart that makes it readable. tests/scenario_exception_hooks.py covers all four hook surfaces, the asyncio handler and batch done-callback, idempotence, the re-entrancy fallback, and the SIGQUIT dump-to-file path in a child process. Suite: 33/33.
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 11, 2026
feat(logging): central exception hooks (StreamController#80) See merge request naz/StreamController!1
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 12, 2026
…ll log sinks The issue-StreamController#80 central exception hooks route full tracebacks into logs.log (with backtrace=True, diagnose=True), so frame paths under /home/<user>, standalone usernames and credentialed URLs now reach disk unredacted -- users cannot safely share their logs (issue StreamController#105, upstream StreamController#439). Add src/backend/log_redaction.py: a stdlib-only scrub() (compiled regexes, single pass each) plus a core-level loguru patcher. The patcher runs before every handler.emit, so one install covers logs.log, stderr, the gl.logs ring and the enqueue'd plugins.log sink, across log.remove()/add() cycles. Because sinks format {exception} from the raw exc tuple themselves, the patcher formats the traceback (stdlib, chained), scrubs it, folds it into the message and clears record["exception"] -- no sink ever sees raw frames. Deliberate side effect: diagnose=True local-variable dumps (their own PII vector) no longer reach any sink. Redacted: home dir (expanduser/realpath/$HOME variants, boundary-guarded both sides) -> "~"; username only as a path segment or in user@host; scheme://user[:pass]@host -> scheme://***@host; secret params (token=, access_token=, api_key=, password=, ...) -> =***; Bearer tokens. Preserved: hosts, URL paths, ~-relative project paths, source lines, exception types/messages -- logs stay debuggable. Installed at the top of main() beside install_exception_hooks(); tests/scenario_log_redaction.py exercises the real hook + file-sink pipeline and the scrub unit cases (34/34 harness green). Closes StreamController#105
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 12, 2026
…longer freezes the deck (StreamController#1) The writer thread (sole path for paints/brightness/Clear/ClearAndClose since the single-writer migration) ran unguarded: any uncaught exception killed it, freezing the deck until replug while control_q grew unbounded. run() now delegates to _run_one_tick() inside a guarded loop: a tick exception is logged with full traceback (rate-limited to 1/5s with a suppressed-repeat count -- local limiter until StreamController#91), _stop is honored in the except path so a failing body can't strand close(), and a 0.25s wake-event backoff bounds a persistent failure at ~4 retries/s instead of a 100% spin. @log.catch on run() would log once and let the thread die -- the guard must live inside the while. Composes with StreamController#80: the hooks report an escaping death; this prevents the death. The five audit-verified crash vectors are also fixed at the source: (a) init_inputs is build-then-swap and the screensaver drops its `inputs = {}` pre-clear (was an empty-dict window on every screensaver entry); the loop snapshots the dict once per tick and uses .get(); (b) the loop snapshots background.video once (set_video(None) race); (c) touchscreen get_current_image guards a nulled active_page (blank strip); (d) the touchscreen tick gate snapshots state.background_video (_release_background_video race); (e) _exec_clear snapshots the touchscreen slot and nulls it identity-compared -- the crash half of StreamController#1 and this site's half of StreamController#8. tests/scenario_writer_survival.py (first leg of StreamController#61) runs the REAL loop over the stub tier: survives a poisoned tick, resumes paints, caps the log storm, and stop() joins while every tick raises. fixtures gains _QuietInputState (get_active_state) since the live loop path never executed on stubs before (StreamController#69 stub-drift). Suite: 34/34.
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 26, 2026
update_ui_on_page_change resolved ...page_settings.settings_page -- an attribute that never existed after the settings restructure -- so it raised AttributeError on EVERY page change, a blanket except mislabeled it as 'okay if you just activated your first deck', and nothing in the body ever ran. Confirmed hypothesis from the 2026-07-17 field diagnosis: chronic since the restructure, in every boot (and it cost real diagnosis time by masquerading as a fresh regression). Of the four things the body tried, three are dead by design in this UI generation: the page-scoped PageSettings/DeckGroup is unreferenced, the deck-scoped Brightness/Screensaver rows have no per-page state and reload on map, and BackgroundMediaRow.load_defaults_from_page is a deliberate dead early-return. The method now does the one meaningful thing -- sidebar.update() -- scoped to the VISIBLE deck (via get_own_deck_stack_child, so a background deck's page change cannot reload the sidebar), with no except wrapper: this runs on the GLib main loop, where a real failure reaches the StreamController#80 central hooks. scenario_page_change_sidebar_sync fakes the real PageSettingsPage shape (no settings_page anywhere), so reviving the dead chain fails the test; it also pins the visible-deck guard and the headless no-op. Closes StreamController#157
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Aug 9, 2026
The StreamController#80 hooks are process-global behavior (sys/threading/unraisable hooks + the asyncio handler + faulthandler redirection). Answering "does this reproduce without the hooks?" meant reverting and rebuilding; now it is an env var, following the SC_STRONG_CALLBACKS / SC_MEM_TELEMETRY / SC_REEXEC convention (read once at import -- a mid-run flip would leave half the hooks installed). Two judgement calls inside the flag's blast radius: * the issue-StreamController#105 redaction patcher stays installed. install_exception_hooks() is its ONLY boot-path install site, so folding it into the opt-out would ship a flagged run with no PII scrubbing on ANY log line -- a debugging switch must not double as a privacy regression; * asyncio_exception_handler() honors the flag by delegating to loop.default_exception_handler(). It is one of the four StreamController#80 surfaces, but it is wired from event_dispatch rather than from the install path, so the opt-out has to live in the handler itself; delegating (rather than returning) is what makes the flag pre-StreamController#80 behavior exactly. scenario_exception_hooks gains a flagged child process: the three interpreter hooks stay stock, _installed never latches, no faulthandler.log (or even its directory) is created, the asyncio surface falls back to asyncio's own handler, and the redaction patcher is still in place.
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Aug 9, 2026
…amController#91) A GTK signal handler is not disconnected when it raises, so a broken handler on a frequent signal (notify::, a 20-30Hz media tick) reached sys.excepthook on every emission, and one broken __del__ hits sys.unraisablehook once per object during a GC sweep -- ~30 full diagnose=True tracebacks per second into logs.log and into the About-dialog ring. The StreamController#80 safety net's first big catch would have DoS'd the log it reports to. _log_exc() now allows one record per site per RATE_LIMIT_WINDOW_S (5s), where a site is (exception type name, innermost traceback frame's file and line). Repeats inside the window are counted and reported as a summary riding on that site's next record. Per-site and never global, so a storm on one handler cannot mask an unrelated one-shot failure; the first hit of any site is always logged immediately. Placed in _log_exc rather than in the individual hooks, so all four StreamController#80 surfaces inherit it. The type NAME rather than the type object keeps a plugin's exception class (and its module) from being pinned by the key. Tracebackless callers -- the asyncio message-only context -- key on the message text instead, so two unrelated tb-less failures still get independent budgets. The dict is pruned once past 256 keys (expired windows first, then the oldest half) so a broad storm cannot turn the guard into a leak, and a trailing count from a storm that simply stops goes unreported rather than being flushed by a timer: these hooks own no thread and must not grow one. Thread safety: the window test is a read-modify-write over shared state, and _log_exc runs on whatever thread failed -- any worker, the main/GLib thread, a GC-sweeping thread, the dispatch loop. It takes an RLock, not a Lock: GC can fire on an allocation inside the guarded region and a raising __del__ collected there re-enters the module on the same thread, which a plain Lock would deadlock on inside the crash handler. The lock is never held across the loguru call, and the whole guard is wrapped so a failure inside it falls through to logging -- it may drop repeats, never an original. scenario_exception_hooks gains: a 100x storm from one site (one record, then the suppressed-99 summary on the next), two interleaved sites that keep separate budgets, four distinct non-repeating failures that log untouched with no summary noise (including via sys.excepthook), a same-line main-thread storm, and a bound check on the state dict under 1024 distinct sites.
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Aug 9, 2026
Review M3. The flag accepted "any value except empty/0", so SC_NO_ERROR_HOOKS=false / no / off -- what an operator writes to turn a switch OFF -- silently disabled the entire StreamController#80 safety net on the run that was trying to keep it. Now strictly "1", matching weak_callbacks.py's SC_STRONG_CALLBACKS precedent and #92's literal spec. Also: a flagged run now says so, exactly once. The line is emitted from redirect_faulthandler() rather than install_exception_hooks() because main() installs the hooks BEFORE config_logger() opens logs.log and the About ring -- an announcement there would be stderr-only, lost on precisely the detached autostart/flatpak runs this flag exists to debug. Scenario: the flagged child asserts the announcement appears once across two redirect calls; five more children (0, false, off, no, empty) assert the hooks are installed regardless.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.