stack-gotchas — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited stack-gotchas (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Diagnose-and-recover recipes for failures I've actually hit. Each is symptom → what it really means → fix — not a tutorial. Match the symptom, apply the recipe.
github-actions[bot]token, _not_ a permissions bug. Tell-tale: a release succeeded seconds earlier; gh api rate_limit core looks healthy. Usually triggered by a burst (dependabot opening ~12 PRs).
gh release create vX.Y.Z --target <full-sha> --title vX.Y.Z --notes "<CHANGELOG section>" --latest. (2) Swap the PR label autorelease: pending → autorelease: tagged via REST (gh api), or it retries forever. (3) Let the bot cooldown clear (~minutes–1h) with no more content-creation calls. Code usually deployed anyway (Pages runs on push independently).
GITHUB_TOKEN) gets no CI run → the requiredverify check never appears → auto-merge can't complete; it lands only via a fragile fast-forward that breaks if main advanced by hand in parallel.
main HEAD(gh release create vX.Y.Z --target <main-sha>) → no pending release → no re-dispatch → loop stops. Replicate the bump in package.json + .release-please-manifest.json + CHANGELOG.md via a normal user PR so CI runs. Don't hand-merge `main` mid-release. Durable fix: give release-please a PAT (its PRs then trigger CI) and drop the unconditional self-redispatch.
fails almost immediately (~15s) with release-please failed: GitHub Actions is not permitted to create or approve pull requests. The CI workflow is fine; only release PR creation fails.
default_workflow_permissions=read + can_approve_pull_request_reviews=false. release-please must open its release PR, and the repo/org toggle overrides the workflow-level permissions: pull-requests: write — so the in-workflow grant is not enough on its own.
and approve pull requests"), via API:
gh api -X PUT repos/<owner>/<repo>/actions/permissions/workflow \
-f default_workflow_permissions=write -F can_approve_pull_request_reviews=true
gh run rerun <failed-release-run-id>Do this right after gh repo create for any new FAP / sister app — it's a one-time per-repo setting, not a code or token problem. (Distinct from the secondary rate-limit and auto-merge-loop failures above.)
github-pages env'sdeployment-branch-policy still lists only the old branch (master).
gh api -X POST repos/<owner>/<repo>/environments/github-pages/deployment-branch-policies -f name=main
gh api repos/<owner>/<repo>/environments/github-pages/deployment-branch-policies --jq '.branch_policies[]'
gh api -X DELETE repos/<owner>/<repo>/environments/github-pages/deployment-branch-policies/<id>Set the default branch before the first deploy; ensure Pages build_type=workflow.
Windows Edge headless clamps window width to ~500px (screenshots <450px render a wider layout cropped — misleading), and puppeteer can't drive the Windows .exe from WSL (stdio pipe breaks; its CDP debug port is unreachable over NAT).
libnss3/libnspr4/libasound2 and there's no passwordless sudo.
npx -y @puppeteer/browsers install chrome@stable # native linux chromium
apt-get download libnss3 libnspr4 libasound2t64 # download .deb, no sudo
for d in *.deb; do dpkg -x "$d" root; done # extract libs locally
# run with: LD_LIBRARY_PATH=$PWD/root/usr/lib/x86_64-linux-gnu <chrome> …Drive via puppeteer-core (executablePath = that chrome; LD_LIBRARY_PATH in the env). Then setViewport({ width, deviceScaleFactor: 2, isMobile: true }) at any width, measure scrollWidth/getBoundingClientRect, bisect overflow by toggling display:none per child, and confirm a fix by injecting the style and re-measuring before editing. Keep the env until done — don't re-download the ~150MB browser mid-task.
deploy or cached PWA** (check the live tag + that autoUpdate activated). Much of this class of confusion is version lag, not a bug.
blob_size × updates × connected_clients.Usually an unbounded field in a JSON blob growing quadratically (history/trash snapshots).
only {version} over Realtime and version-gate full fetches; write-through the local cache. See eskills:perf-bar. Next lever: split the growing field into its own table.
SELECT true + INSERT/UPDATE true; client-side PIN/rate-limit giveno server protection (the anon key ships in the client).
eskills:security-bar.
edits.
cached client (running old code) drops fields its schema doesn't know on its next write. The optimistic version check doesn't protect (same-version-lineage overwrite).
_schemaVersion on every write + a Postgres BEFORE UPDATE triggerrejecting writes whose version < stored (→ HTTP 426 → "please update" prompt); flip the PWA to autoUpdate. Operational rule: bump `SCHEMA_VERSION` whenever the snapshot shape changes, or the guard won't protect the new field.
application.fam'sfap_version, OR include/version.h's constant — while the others bump → catalog/CI mismatch, and an in-app "version" string shows the stale number.
generic updater only replaces a version on a line that itself carriesthe x-release-please-version marker. The marker must be INLINE on the same line as the value — fap_version="x.y.z" # x-release-please-version and #define APP_VERSION "x.y.z" // x-release-please-version. A marker on a separate line above the #define (or a missing/edited marker) makes the updater silently no-op that file. (Hit on flipper-tutu: version.h had the marker on its own line and stuck at 0.1.0 while fam/manifest advanced.)
current version once by hand; keep the triad application.fam ↔ include/version.h ↔ .release-please-manifest.json aligned. See stacks/references/flipper/release-please.md.
commit_sha points at a later fix commit instead of thetagged release commit, or the entry version ≠ fap_version.
commit_sha at the release commit; make the catalog version equalfap_version. (Catalog-update-flow memory + stacks → flipper.)
make test fails to compile with undefined furi references.furi — furi isn't available on thehost gcc build; the layering leaked.
domain/ pure C and move the furi-touching code behind a plain-C signaturein platform/. Only test domain/. See stacks/references/flipper/architecture.md.
make linter errors on unusedFunction for main.c / *_app.c / port files.false positive, not dead code.
--suppress=unusedFunction:<path> (or inline// cppcheck-suppress), never disable the check globally. See stacks/references/flipper/formatting.md.
from a desktop favourite or a quick-press button shortcut — but it works fine when opened from the Apps menu. (Reported by catalog users on two of my apps.)
view_port_update() in reaction to input — the main loopblocks on furi_message_queue_get(..., FuriWaitForever) and paints nothing until a key is pressed. The Apps menu masks it: the loader's background hourglass animation forces a GUI redraw, so the first frame appears. Favourites/quick-buttons skip that loader → nothing triggers the draw. The app was leaning on an external redraw ("a bug"), not driving its own.
gui_add_view_port(...) with view_port_update(view_port). (2) Don't depend on input to paint: either give the queue a finite timeout (furi_message_queue_get(q, &e, 50)) and call view_port_update() every loop tick, or call it after every state change (including the initial state). Same fix for a ViewDispatcher/SceneManager app: ensure the first scene transition happens before the loop blocks. See stacks/references/flipper/architecture.md.
financial data (e.g. EventSplit's events blob).
SELECT/INSERT/UPDATE true) isn't just a security bug — it's apersonal-data exposure / breach risk under GDPR (unauthorized access to identifiable people, including third parties who never consented).
eskills:security-bar); then in the privacy notice, only claim protections you actually have. Don't document "your data is protected" while RLS is open. See eskills:gdpr.
privacy notice (if any) doesn't mention it (mintza).
transfer mechanism named — a GDPR gap, and possibly special-category data (voice).
the notice; confirm no-training-on-API-data + retention; send the model the minimum content, no needless identifiers. See eskills:gdpr (AI-processor posture).
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.