[REVIEW] Full-codebase Greptile audit - #1
Conversation
…; 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.
…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
|
@greptileai Please perform a comprehensive full-codebase audit covering:
|
| changed = (sig != self._last_render_sig) or any_mark_animating | ||
| self._last_render_sig = sig | ||
| if not changed: | ||
| return None | ||
| return r |
There was a problem hiding this 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.
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.
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.| 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)) |
There was a problem hiding this 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.
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)`.
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.| 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 |
There was a problem hiding this 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.
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.
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.| 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 |
There was a problem hiding this 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.
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.
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!
Overview
Not a normal feature PR — this diffs the entire current codebase against a
disposable, empty baseline branch (
greptile-audit-base, an orphan commitwith 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-trustPR #7 and #9, adaptedfor 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): thepygoose/package (engine, goose behavior state machine, config, cursortracking, 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
codebase — this is intentionally broad, not scoped to one recent change.
pygoose/goose/goose.py, the largestfile) — check for state-transition bugs, race conditions between timers
and user input, and any resource leaks in long-running behavior loops.
meme_window.py,movable_window.py,notepad_window.py) — check for consistent cleanup/teardown, especiallyaround window close and app shutdown.
config.pyandpaths.py— check for path-handling issues that couldbreak on different OS/user setups.
anything that reads/writes files or executes commands without proper
path validation.
Not for merging
greptile-audit-baseis a disposable orphan branch that exists only togive 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-basebranch 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.pyneeds repaint invalidation and forced-task error handling;pygoose/goose/cursor.pyneeds global cursor coordinates on macOS;.github/workflows/release.ymlneeds immutable action pins.Security Review
The release workflow invokes
softprops/action-gh-release@v2through a mutable tag while grantingcontents: 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.What T-Rex did
Comments Outside Diff (4)
General comment
(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 framesGoose.dirty_rect()returnedNone, which makesOverlay._tick()skipupdate()when the goose is otherwise stationary. The painted prop therefore remains frozen until another signature input changes, then appears at its later state.sigcontains goose, feet, rig, and sleep values but no prop state. Lines 421-425 compare only that signature and footmark animation state.General comment
set_cursor_clipinvokesQCursor.setPoswith 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.pygoose/goose/cursor.py:36computes onlyx + w / 2andy + h / 2; it does not addQGuiApplication.primaryScreen().geometry().x()and.y()before calling the global-coordinateQCursor.setPosoverload.QCursor.setPos, for example by adding the primary screen geometry'sx()andy()offsets (or ensure all inputs are made global at the call boundary).General comment
DEV_ForceTaskvalue causes_choose_next_taskto raiseValueErrorinstead of selecting a task, aborting the behavior-loop call path.pygoose/goose/goose.py:690directly constructsTask(self.config.dev_force_task)with no validation orValueErrorhandling.Taskvalues (or catchValueError) and fall back to normal task selection orTask.WANDER; optionally surface a non-fatal warning.General comment
.github/workflows/release.yml:85:softprops/action-gh-release@v2is referenced by mutable major-version tagv2, not a reviewed immutable 40-character commit SHA. The workflow is tag-triggered and hascontents: writeat.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.softprops/action-gh-releaseaction to a mutable version tag rather than a specific reviewed commit SHA while assigning repository write capability.softprops/action-gh-release@v2at 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.Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "release: v0.37 — knife threat, SilenceMu..." | Re-trigger Greptile