Skip to content

[REVIEW] Full-codebase Greptile audit - #1

Draft
claudepc42 wants to merge 65 commits into
greptile-audit-basefrom
main
Draft

[REVIEW] Full-codebase Greptile audit#1
claudepc42 wants to merge 65 commits into
greptile-audit-basefrom
main

Conversation

@claudepc42

@claudepc42 claudepc42 commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Overview

Not a normal feature PR — this diffs the entire current codebase against a
disposable, empty baseline branch (greptile-audit-base, an orphan commit
with zero files), so the full project shows up as one diff for a
comprehensive first-ever Greptile pass, rather than reviewing it piecemeal
across incremental PRs. Same trick used for
claudepc42/audiobookshelf-app-cloudflare-zero-trust PR #7 and #9, adapted
for a project with no upstream fork to diff against — the baseline here is
a genuinely empty orphan branch instead of a copied upstream/old-tag branch,
since PyGoose is an original project, not a fork.

Scope

The entire PyGoose codebase as of main (95 files, ~7,371 lines): the
pygoose/ package (engine, goose behavior state machine, config, cursor
tracking, overlay rendering, renderer, sound, window subsystems — meme
window, movable window, notepad window), plus tests/test_engine.py.

Testing Done

Not applicable in the usual sense — this is a first full-codebase audit
pass, not a review of a specific recent change. Existing test coverage is
in tests/test_engine.py.

Specific Concerns for Review

  1. General code quality, bugs, and architectural issues across the whole
    codebase — this is intentionally broad, not scoped to one recent change.
  2. The goose behavior state machine (pygoose/goose/goose.py, the largest
    file) — check for state-transition bugs, race conditions between timers
    and user input, and any resource leaks in long-running behavior loops.
  3. Window subsystems (meme_window.py, movable_window.py,
    notepad_window.py) — check for consistent cleanup/teardown, especially
    around window close and app shutdown.
  4. config.py and paths.py — check for path-handling issues that could
    break on different OS/user setups.
  5. Security: this is a desktop app running arbitrary local behavior — flag
    anything that reads/writes files or executes commands without proper
    path validation.

Not for merging

greptile-audit-base is a disposable orphan branch that exists only to
give this PR something to diff against — it has no relationship to the
project's real history. This PR itself is not intended to be merged; close
both this PR and the greptile-audit-base branch once the review is done.

Greptile Summary

This change adds a cross-platform desktop-goose application, including its Qt overlay, behavior state machine, props, cursor control, packaging, and release automation.

Executable checks reproduced four problems that should be fixed before merge: animated props can stop repainting while the goose is idle; macOS cursor capture can target the wrong virtual-desktop coordinates; an invalid developer-forced task aborts behavior selection; and the release publisher runs a mutable third-party action with repository write permission.

Merge safety: unsafe until the identified behavior, cursor, configuration, and release-publishing issues are resolved.

Confidence Score: 3/5

The change is not safe to merge because it contains reproducible desktop behavior failures and a release-publishing supply-chain exposure.

Independent executable checks reproduced a skipped repaint path, an incorrect macOS global-coordinate calculation, an uncaught configuration exception in task selection, and mutable third-party code running with release write access.

Files Needing Attention: pygoose/goose/goose.py needs repaint invalidation and forced-task error handling; pygoose/goose/cursor.py needs global cursor coordinates on macOS; .github/workflows/release.yml needs immutable action pins.

Security Review

The release workflow invokes softprops/action-gh-release@v2 through a mutable tag while granting contents: write. If that upstream tag changes, unreviewed action code can execute with permission to publish or modify release artifacts. Pin both release-action invocations to reviewed full commit SHAs.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced a P1 finding proof about the narrow production prop animation and dirty-rect harness affecting repaint decisions, supported by before- and after-render data.
  • T-Rex validated macOS cursor origin handling by running the non-zero-origin cursor test harness and comparing baseline against non-zero origin results.
  • T-Rex compared DEV ForceTask outputs, distinguishing the invalid observed output from the valid control path, with harness outputs attached.
  • T-Rex validated the release workflow pin by executing the validator and confirming the exact release workflow source and the assertion-backed pin outcome.
  • T-Rex performed general contract validation confirming the prop repaint decision path and the resulting render, including before and after evidence of the prop position.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (4)

  1. General comment

    P1 Animated props are omitted from the repaint signature

    • Bug
      • After the initial paint, a falling prop changed from (330.0, 205.0, z=46.0, angle=0.0) to (357.0, 205.0, z=0.0, angle=108.0) across six production physics ticks. On every one of those frames Goose.dirty_rect() returned None, which makes Overlay._tick() skip update() when the goose is otherwise stationary. The painted prop therefore remains frozen until another signature input changes, then appears at its later state.
    • Cause
      • sig contains goose, feet, rig, and sleep values but no prop state. Lines 421-425 compare only that signature and footmark animation state.
    • Fix
      • Include all render-affecting prop state in the dirty/repaint decision—at minimum type, position, surface_z, z, angle, scale, state, and any rendered prop-specific data—or explicitly mark active/falling/animated props dirty and invalidate a rectangle covering both their old and new rendered bounds.

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P1 macOS cursor placement omits the primary display's virtual-desktop origin

    • Bug
      • When the primary macOS display begins at a non-zero virtual-desktop coordinate, set_cursor_clip invokes QCursor.setPos with local gameplay coordinates. Qt interprets them as global coordinates, so the cursor is displaced by the primary display origin and can land on a different display.
    • Cause
      • pygoose/goose/cursor.py:36 computes only x + w / 2 and y + h / 2; it does not add QGuiApplication.primaryScreen().geometry().x() and .y() before calling the global-coordinate QCursor.setPos overload.
    • Fix
      • Convert the primary-display-local center to global coordinates before calling QCursor.setPos, for example by adding the primary screen geometry's x() and y() offsets (or ensure all inputs are made global at the call boundary).

    T-Rex Ran code and verified through T-Rex

  3. General comment

    P1 Invalid DEV_ForceTask crashes next-task selection

    • Bug
      • A stale, misspelled, or unsupported non-empty DEV_ForceTask value causes _choose_next_task to raise ValueError instead of selecting a task, aborting the behavior-loop call path.
    • Cause
      • pygoose/goose/goose.py:690 directly constructs Task(self.config.dev_force_task) with no validation or ValueError handling.
    • Fix
      • Validate against supported Task values (or catch ValueError) and fall back to normal task selection or Task.WANDER; optionally surface a non-fatal warning.

    T-Rex Ran code and verified through T-Rex

  4. General comment

    P1 Mutable release-publishing action is granted repository contents write permission

    • Bug
      • Confirmed at .github/workflows/release.yml:85: softprops/action-gh-release@v2 is referenced by mutable major-version tag v2, not a reviewed immutable 40-character commit SHA. The workflow is tag-triggered and has contents: write at .github/workflows/release.yml:9; it uploads release files at lines 86–89. If the action's upstream tag changes, the altered action code executes with permission to publish or modify release contents.
    • Cause
      • The workflow pins the third-party softprops/action-gh-release action to a mutable version tag rather than a specific reviewed commit SHA while assigning repository write capability.
    • Fix
      • Replace softprops/action-gh-release@v2 at line 85 (and the identical line 110 invocation) with the reviewed full 40-character commit SHA for the intended action release, and retain the SHA pin through dependency updates.

    T-Rex Ran code and verified through T-Rex

Fix All in Claude Code

Prompt To Fix All With AI
### Issue 1
pygoose/goose/goose.py:421-425
**Animated props are omitted from the repaint signature**

`dirty_rect()` only compares goose, rig, foot, and sleep state. It does not account for `self.props`, even though prop physics and rendering continue independently. Once the goose is stationary, a falling or settling prop can change position, height, or angle while this method returns `None`, so the overlay does not repaint it. The prop remains visually frozen until another goose change triggers a paint, then jumps to its later position. Include active prop state in invalidation, or explicitly invalidate both the previous and current bounds of animated props.

### Issue 2
pygoose/goose/cursor.py:36
**macOS cursor placement omits the virtual-desktop origin**

`QCursor.setPos` expects global virtual-desktop coordinates, but this passes the center of the supplied display-local rectangle directly. When the primary display has a non-zero virtual-desktop origin, the cursor is displaced by that origin and can land on the wrong display. Convert the local center to global coordinates by adding the target screen geometry's `x()` and `y()` offsets before calling `setPos`.

### Issue 3
pygoose/goose/goose.py:688-691
**Invalid forced tasks crash task selection**

A stale, misspelled, or unsupported non-empty `DEV_ForceTask` value is passed directly to `Task(...)`. The enum constructor raises `ValueError` before `_set_task` runs, aborting the timer-driven behavior update instead of allowing the goose to continue normally. Validate the configured value or catch `ValueError` and fall back to normal task selection or `Task.WANDER`.

### Issue 4
.github/workflows/release.yml:85
**Mutable release action has repository write access**

`softprops/action-gh-release@v2` is a mutable third-party tag rather than a reviewed immutable commit SHA. This tag-triggered workflow grants `contents: write`, so a changed upstream tag could execute altered code with permission to publish or modify release artifacts. Pin this invocation and the identical source-release invocation to reviewed full 40-character commit SHAs.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "release: v0.37 — knife threat, SilenceMu..." | Re-trigger Greptile

Greptile also left 4 inline comments on this PR.

ClaudePC and others added 30 commits June 10, 2026 17:10
…; update PRD and README

- New tasks: WATCH_MOUSE, FOLLOW_MOUSE, SNEAK_ATTACK, SLEEP, PEEK_BACK
- SLEEP: spiral circling at constant arc speed, settle pose, 90s–8min duration
- Fake sleep: 15% chance, eye peeking, cursor-spotted freak-out sequence with exclamation mark
- Freak-out: bounce off-screen at CHARGE speed for 4s, honking every 0.3s
- PEEK_BACK: crawl to edge, sine-sweep gaze with smoothstep envelope, progressive stand-up walk-in
- SNEAK_ATTACK: crawl approach, pounce transition, drag sequence
- FOLLOW_MOUSE: preferred distance following, flee, honk march
- WATCH_MOUSE: sit/stand/walk sub-states, head bob, petting reactions
- SNEAK speed tier (28px/s, 0.45s step)
- Rig: sit_lerp_percent, neck_tuck_lerp_percent, is_sleeping, show_sleep_bubbles, peek_eye, show_exclamation, sleep_phase
- Renderer: crawl pose, sleep bubbles, exclamation mark, eye visibility logic
- Foot home adapts to crawl pose (perp spread + drop)
- DEV flags: DEV_FORCE_TASK, DEV_SHORT_WANDER, DEV_FORCE_FAKE_SLEEP
- PRD renamed from .md.txt to .md; fully updated with all new systems
- README updated with all behaviors, fake sleep description, dev flags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pygoose/paths.py: resource_path() for bundled assets, user_data_path() for user-editable content
- All asset opens updated to use path helpers; works from source and frozen exe
- config.ini resolves next to exe when frozen, project root when running from source
- Sounds/fonts bundled inside exe; memes/notes folders created next to exe by build.bat
- PyGoose.spec: one-folder build, includes assets/sounds and assets/fonts
- build.bat: runs PyInstaller and creates empty user asset folders in dist
- .gitignore: unexclude PyGoose.spec (intentional, not auto-generated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- overlay.py: ESC quit with progress bar; polls GetAsyncKeyState each frame (reliable through click-through window)
- overlay.py: remove dead _esc_held field and unused Qt/DELTA_TIME imports
- paths.py + all asset loaders: resource_path/user_data_path helpers for PyInstaller compatibility
- build.bat: copy memes and notepad messages to dist alongside exe
- CLAUDE.md: no git pushes without explicit direction
- PyGoosePRD.md: packaging section (§34), future features (§35)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix missing Qt import in overlay (crashed on startup)
- Drop timer interval from 8ms to 16ms (60Hz render, 120Hz physics via two ticks/wake)
- Add dirty-rect partial repaints — only repaint goose bounding box + active footmarks
- Wire goose.dirty_rect() into overlay via set_dirty_rect_fn()
- Reduces CPU from ~9% to ~2.3% at idle
- Bump version to 0.32 in PRD and README badge
- Add PRD sections: floating menu button idea (35.3), known issues (36), ESC flakiness in exe (36.1)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Builds Windows, macOS, and Linux executables automatically on version tags.
Also packages a Python source zip. All four artifacts upload to the GitHub release.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
libgl1-mesa-glx and libegl1-mesa were removed; use libgl1 and libegl1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use Compress-Archive on Windows runner instead of zip (not available).
Add no-attribution rule to CLAUDE.md.
Performance:
- 60 Hz render / 120 Hz sim decoupling (frame-skip on identical frames)
- Identical-frame skip: dirty_rect() returns None when nothing changed
- Music PCM cache: decode MP3 once to WAV, near-zero decode cost thereafter
- Render object caching: pens, brushes, Qt enums cached in renderer.py
- Per-tick import hoisting in goose.py hot paths
- Static CPU: ~12.5% -> ~0.5% of one core (96% reduction)

Multi-window collect:
- Keep up to 2 memes and 2 notepads on screen simultaneously
- Eviction drag: when 2 of a type exist, goose grabs one by the edge
  and drags it offscreen before fetching the new window
- Placement spread: wider random range, messy overlap for 2nd window
- Edge-grab walk: goose targets the grab edge, not the window center
- Meme and notepad decks are module-level, cycle before repeating
- Notepad task weight tripled in rotation (1x -> 3x)
- overlay.py: call setIgnoresMouseEvents_ on NSWindow via PyObjC after
  show(); WA_TransparentForMouseEvents alone is insufficient on macOS
- cursor.py: implement set_cursor_clip on macOS with QCursor.setPos()
  instead of pass stub; works once Accessibility is granted
- main.py: startup checks on macOS — prompt to install
  pyobjc-framework-Quartz if missing, prompt to grant Accessibility
  permission if AXIsProcessTrusted() returns False
- PyGoose.spec: macOS builds use --onefile to avoid Gatekeeper approving
  every dylib individually; Windows/Linux stay as --onedir
- README, PRD: document macOS requirements and behavior
- PyGoose.spec: add Quartz, AppKit, objc to hiddenimports on macOS so
  the packaged app requires no extra installs
- main.py: only show the missing-Quartz dialog when running from source
  (sys.frozen is False); packaged builds always have it bundled
- Install pyobjc-framework-Quartz before PyInstaller on macOS so the
  hidden imports (Quartz, AppKit, objc) are actually present to bundle
- macOS onefile build outputs dist/PyGoose as a file, not a folder;
  restructure it into a folder containing the binary + user asset dirs
  so the zip step works the same as Windows/Linux
Quartz does not expose AXIsProcessTrusted — it lives in ApplicationServices.
Rather than adding another pyobjc framework dependency, call it directly
via ctypes from the system ApplicationServices framework (stdlib only).
Quartz import is retained for is_left_mouse_down in cursor.py.
- PyGoose.spec: replace onefile EXE with proper BUNDLE for macOS so
  Finder launches the app directly without opening a Terminal window;
  .app also appears as "PyGoose" in Accessibility settings instead of
  "Terminal"
- overlay.py: set NSApplicationActivationPolicyAccessory so the overlay
  stays visible when focus moves to another app (was hiding on click)
- paths.py: _user_root() now walks up past the .app bundle boundary so
  user assets are found next to PyGoose.app, not inside it
- release.yml: update macOS packaging step for .app output layout
- cursor.py: replace Quartz import with ctypes CoreGraphics call;
  no pyobjc needed for mouse button detection
- overlay.py: replace AppKit import with ctypes ObjC runtime calls
  for setActivationPolicy and setIgnoresMouseEvents; no pyobjc needed
- main.py: remove stale Quartz availability check; simplify macOS
  startup to only the AX trust prompt
- PyGoose.spec: remove pyobjc hiddenimports; bundle is now pyobjc-free
- release.yml: remove pyobjc CI install step; add ad-hoc codesign step
  (fixes segfault on macOS 26 ARM64 caused by unsigned bundle failing
  CFBundleCopyBundleURL PAC check); switch macOS archive to tar.gz
  for better compression of the .app bundle structure
The PyInstaller BUNDLE format triggers a Qt static initializer crash on
macOS 26 (NULL NSBundle from CFBundleCopyBundleURL). The original onefile
binary ran without this issue; the wrapper .app avoids it by running the
onefile binary in a background nohup process, keeping the same execution
context that was already working.

Distribution layout:
  PyGoose.app/Contents/MacOS/launcher  <- shell script (CFBundleExecutable)
  PyGoose.app/Contents/MacOS/PyGoose   <- onefile binary (all deps inside)
  assets/                              <- user-editable content

Gatekeeper approves the .app once; --deep codesign covers the binary inside.
paths.py _user_root() walks up past the .app to find assets correctly.
Binary inside .app/Contents/MacOS/ still gets NSBundle.mainBundle set to
the .app bundle, triggering the same Qt static initializer crash on macOS 26.
Binary must live next to the .app so it launches with no bundle context.

Launcher script now goes three levels up from Contents/MacOS/ to find the
binary, and runs xattr -cr on it to clear quarantine before launching.
setHidesOnDeactivate: False prevents the NSPanel from hiding when another
app gains focus. Replace shell script launcher with a compiled C stub so
macOS 26 doesn't silently block the .app CFBundleExecutable.
ClaudePC added 28 commits June 12, 2026 21:24
…onfig.ini

Remove the isatty() guard from _detach_from_terminal() — PyInstaller onefile
binaries have no tty so the guard always bailed before the osascript ran.

Move DEV_FORCE_TASK / DEV_SHORT_WANDER / DEV_FORCE_FAKE_SLEEP from hardcoded
module constants in goose.py into config.ini (auto-generated on first launch).
All three default to off; edit config.ini and restart to apply.
Replaces the osascript 'do script exit' approach which couldn't inject
into a blocked shell tab. SIGHUP to the parent PID triggers Terminal's
clean window-close behavior. Frozen-binary only — safe for dev runs.
raise_() was called on every position update (120x/sec), repeatedly
stealing input focus on Windows. WindowStaysOnTopHint handles z-order.
Clear dev_force_task back to empty string in config.py before any stable release tag.
Stable builds generate a clean ini with no DEV lines.
Dev builds with non-default flags still write them as before.
Added SHOWING_WINDOW stage with 0.4s delay on macOS before dragging starts,
giving the compositor time to render the window. No delay on Windows/Linux.
macOS enforces a minimum visible area and hides windows positioned mostly
off-screen. Work with this instead of against it: show the incoming window
only when its leading edge enters the screen, and hide the evict window as
soon as it reaches the screen edge rather than continuing off-screen.
Removed raise_() and activateWindow() from show_dialog on both window types.
Also bumps macOS notepad font default to 32.
Behavior module system
- All goose behaviors extracted from goose.py into behaviors/ modules
- Each module has its own State dataclass, Stage enum, constants, enter(), tick()
- Core loop is clean dispatch via _BEHAVIOR_ENTER/_BEHAVIOR_TICK dicts
- Override slot pattern: behaviors write neutral-default attrs on Goose/Rig; core reads blindly

Prop system — knife
- New files: props/prop.py, props/physics.py, props/prop_renderer.py, behaviors/carry_prop.py
- Goose exits offscreen, re-enters carrying a knife; wanders 5-11s, then places or drops
- Pending drop: on drop path, transitions to next task immediately and drops 0.7s into new walk
- Spinning drop when moving (400-700 deg/s random), straight drop when stopped
- Pickup: walks to existing knife if at cap (2 on screen) instead of fetching new one
- Swap edge case: drops carried knife at feet right before picking up another
- launch_prop_falling/random_spin are shared helpers for all future drop scenarios
- Prop design mode: debug visualization with variant panel, compass ring, shadow previews, sine wave Z test

Task weights: CARRY_PROP 2/18 -> 3/18, COLLECT_WINDOW_NOTEPAD 3/18 -> 2/18

Visual: fake sleep eye adjusted to three-quarters open

Housekeeping: .gitignore updated to exclude stray root-level duplicates and local-only files
Goose now enters a stare-down with the cursor when a knife is on screen.
Approaches slowly, holds position facing the cursor, jabs with neck, and
launches faint lunges. Cursor getting too close triggers a full freak-out
(drops knife, runs). Timeout triggers a triumphant victory lap with
continuous honking before dropping the knife and wandering off.

Also formalises the carry_prop decision model (PRD §12.6.17): each prop
type will register its interactions in PROP_REGISTRY; carry_prop.enter()
reads world state and dispatches accordingly, keeping the deck untouched.
- Replace jab mechanic with watch_mouse-style head bobs during stare-down (0.25-0.7s interval)
- Add head bobs at 2/sec during victory lap
- Reduce victory lap radius to 70-120px (was 47-80, original 140-240)
- Fix stare-down timer to only start when goose arrives at hold distance
- Fix pending drop cancellation when transitioning to KNIFE_THREAT
- Set PassThrough DPI rounding policy before QApplication so Qt uses
  exact scale factors (1.25, 1.5) rather than rounding to integers
- _get_cursor_pos now subtracts screen origin so coords are overlay-local;
  fixes multi-monitor setups and DPI coordinate space mismatches
- ClipCursor now multiplies by devicePixelRatio to pass physical pixel
  coords as required by the Win32 API
- Add SilenceMusic config option (mutes background music, keeps other sounds)
- Knife threat mode: goose picks up or fetches knife, approaches cursor,
  stares it down with rapid head bobs and faint lunges. Cursor gets close
  and he flees in a panic; hold your ground and he celebrates with a
  triumphant honk and a gloating victory lap (knife in beak, honking)
- SilenceMusic config key: mutes background music while leaving honks,
  pats, and other sounds running
- Windows DPI scaling fix: PassThrough rounding policy, screen-relative
  cursor coordinates, and physical-pixel ClipCursor coords — cursor
  tracking now works correctly at 125%, 150%, and other non-100% scales
- Added screenshot to README
@claudepc42

Copy link
Copy Markdown
Owner Author

@greptileai Please perform a comprehensive full-codebase audit covering:

  1. General code quality, bugs, and architectural issues across the whole PyGoose package.
  2. The goose behavior state machine (pygoose/goose/goose.py and pygoose/goose/behaviors/*) - state-transition bugs, race conditions between timers and user input, resource leaks in long-running behavior loops.
  3. Window subsystems (meme_window.py, movable_window.py, notepad_window.py) - consistent cleanup/teardown, especially around window close and app shutdown.
  4. config.py and paths.py - path-handling issues that could break on different OS/user setups.
  5. Security: this is a desktop app running arbitrary local behavior - flag anything that reads/writes files or executes commands without proper path validation.
  6. Physics/props system (pygoose/goose/props/*) and rendering (renderer.py, overlay.py) - performance issues or unbounded resource growth.

Comment thread pygoose/goose/goose.py
Comment on lines +421 to +425
changed = (sig != self._last_render_sig) or any_mark_animating
self._last_render_sig = sig
if not changed:
return None
return r

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Animated props are omitted from the repaint signature

dirty_rect() only compares goose, rig, foot, and sleep state. It does not account for self.props, even though prop physics and rendering continue independently. Once the goose is stationary, a falling or settling prop can change position, height, or angle while this method returns None, so the overlay does not repaint it. The prop remains visually frozen until another goose change triggers a paint, then jumps to its later position. Include active prop state in invalidation, or explicitly invalidate both the previous and current bounds of animated props.

Artifacts

Narrow production prop animation and dirty-rect harness source

  • The executable harness drives production prop physics and production dirty-rect logic with a stationary goose, ending with reproducible assertions of the missing invalidation.

Before execution log with skipped repaint decisions

  • The before run executed the production failure path and recorded six changed falling-prop frames for which production dirty_rect returned None, proving repaint is skipped.

Before rendering evidence showing stationary goose and retained old prop visual

  • The before capture visualizes the executed skipped-repaint result: the stationary goose remains while the prior prop visual is retained instead of following its advanced physics state.

After execution log with prop-inclusive repaint decisions

  • The comparison run recorded that each changed prop frame schedules repaint when the prop state is included in the invalidation decision, showing the required behavior.

After rendering evidence showing the prop at its advanced position with stationary goose

  • The after capture visualizes the prop-inclusive repaint result: the goose stays fixed and the prop is drawn at its final physics position, demonstrating the correction.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: pygoose/goose/goose.py
Line: 421-425

Comment:
**Animated props are omitted from the repaint signature**

`dirty_rect()` only compares goose, rig, foot, and sleep state. It does not account for `self.props`, even though prop physics and rendering continue independently. Once the goose is stationary, a falling or settling prop can change position, height, or angle while this method returns `None`, so the overlay does not repaint it. The prop remains visually frozen until another goose change triggers a paint, then jumps to its later position. Include active prop state in invalidation, or explicitly invalidate both the previous and current bounds of animated props.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

Comment thread pygoose/goose/cursor.py
pass
def set_cursor_clip(x: float, y: float, w: float, h: float):
from PyQt6.QtGui import QCursor
QCursor.setPos(int(x + w / 2), int(y + h / 2))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 macOS cursor placement omits the virtual-desktop origin

QCursor.setPos expects global virtual-desktop coordinates, but this passes the center of the supplied display-local rectangle directly. When the primary display has a non-zero virtual-desktop origin, the cursor is displaced by that origin and can land on the wrong display. Convert the local center to global coordinates by adding the target screen geometry's x() and y() offsets before calling setPos.

Artifacts

Executable harness source for the macOS non-zero-origin cursor test

  • The exact Python source executes cursor.py's Darwin branch with an intercepted QCursor call and compares its coordinates with the required global virtual-desktop location, confirming the origin omission.

Cursor coordinate baseline with zero virtual-desktop origin

  • The executed zero-origin comparison shows line 36 produces `(110, 205)`, which matches the required global coordinate when no origin offset exists, establishing the baseline.

Cursor coordinate result with non-zero primary-display origin

  • The executed Darwin-branch harness records line 36 sending `(110, 205)` while the required global position is `(2030, 205)`, confirming the cursor is offset by `(-1920, 0)`.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: pygoose/goose/cursor.py
Line: 36

Comment:
**macOS cursor placement omits the virtual-desktop origin**

`QCursor.setPos` expects global virtual-desktop coordinates, but this passes the center of the supplied display-local rectangle directly. When the primary display has a non-zero virtual-desktop origin, the cursor is displaced by that origin and can land on the wrong display. Convert the local center to global coordinates by adding the target screen geometry's `x()` and `y()` offsets before calling `setPos`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

Comment thread pygoose/goose/goose.py
Comment on lines 688 to 691
def _choose_next_task(self):
if DEV_FORCE_TASK:
self._set_task(Task(DEV_FORCE_TASK))
if self.config.dev_force_task:
self._set_task(Task(self.config.dev_force_task))
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Invalid forced tasks crash task selection

A stale, misspelled, or unsupported non-empty DEV_ForceTask value is passed directly to Task(...). The enum constructor raises ValueError before _set_task runs, aborting the timer-driven behavior update instead of allowing the goose to continue normally. Validate the configured value or catch ValueError and fall back to normal task selection or Task.WANDER.

Artifacts

Narrow DEV ForceTask harness source

  • The executable harness extracts the exact Task enum and _choose_next_task method from the repository source and invokes the method with a probe config, showing the targeted runtime scope.

Invalid DEV ForceTask execution output

  • The captured command run with DEV_ForceTask set to WanderTypo shows ValueError before any task is set, confirming the crash condition.

Valid DEV ForceTask control execution output

  • The captured command run with DEV_ForceTask set to wander returns normally and sets the wander task, showing the contrasting supported-value behavior.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: pygoose/goose/goose.py
Line: 688-691

Comment:
**Invalid forced tasks crash task selection**

A stale, misspelled, or unsupported non-empty `DEV_ForceTask` value is passed directly to `Task(...)`. The enum constructor raises `ValueError` before `_set_task` runs, aborting the timer-driven behavior update instead of allowing the goose to continue normally. Validate the configured value or catch `ValueError` and fall back to normal task selection or `Task.WANDER`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

run: cd dist && tar -czf DesktopGoose-PyGoose-${{ github.ref_name }}-${{ matrix.platform }}.tar.gz PyGoose

- name: Upload to release
uses: softprops/action-gh-release@v2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Mutable release action has repository write access

softprops/action-gh-release@v2 is a mutable third-party tag rather than a reviewed immutable commit SHA. This tag-triggered workflow grants contents: write, so a changed upstream tag could execute altered code with permission to publish or modify release artifacts. Pin this invocation and the identical source-release invocation to reviewed full 40-character commit SHAs.

Artifacts

Executed narrow workflow action-pin validator

  • The exact dependency-free Python validator executed against the workflow; it checks the tag trigger, contents permission, action line 85, and full-SHA pin requirement, confirming the insecure reference.

Exact release workflow source validated

  • An unmodified snapshot of `.github/workflows/release.yml` used as the validation input; it shows the write permission and mutable release-action references, confirming the affected configuration.

Raw release workflow observation

  • Captured output of the raw runtime workflow inspection, including command, working directory, and exit code; it shows `contents: write` and line 85 `v2` is not an immutable commit SHA, confirming the condition.

Assertion-backed release action pin result

  • Captured output of the assertion-backed runtime validator, including command, working directory, and exit code 0; it reports `RESULT=CONFIRMED`, confirming the security bug.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/release.yml
Line: 85

Comment:
**Mutable release action has repository write access**

`softprops/action-gh-release@v2` is a mutable third-party tag rather than a reviewed immutable commit SHA. This tag-triggered workflow grants `contents: write`, so a changed upstream tag could execute altered code with permission to publish or modify release artifacts. Pin this invocation and the identical source-release invocation to reviewed full 40-character commit SHAs.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

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