| name | bug-finder |
| description | Deeply scans code for runtime bugs, logic errors, and correctness defects in the Nuclia frontend monorepo. Use this skill whenever code needs to be audited for actual bugs rather than style issues — including during code review, when a user reports unexpected behaviour, when writing or reviewing a PR, or when asked to "find bugs", "check for bugs", "is this correct?", or "what could go wrong here?". This skill is intentionally narrow: it reports only things that are broken or will likely break at runtime, not style, architecture, or best-practice nudges (those belong in code-review). Designed to be invoked by automated agents as well as interactively.
|
Bug Finder — Nuclia Frontend Monorepo
Scan code for defects that cause or are likely to cause incorrect behaviour at runtime.
Do NOT flag style issues, naming conventions, or architectural preferences — only genuine bugs.
How to obtain code to scan
- If the user pastes a diff, file content, or code snippet directly, use that.
- For a branch review:
git diff main...HEAD — focus on + lines.
- For a single file: read it directly, then scan the full function bodies of changed or suspect areas.
- When a bug reference points elsewhere (e.g., a service used by the changed component), read that file too.
Bug Catalogue
Work through the changed or provided code against every category below. Report only confirmed or
highly probable defects — not hypothetical ones.
Memory Leaks — RxJS Subscriptions
A subscription that is never cleaned up will keep running after the component/service is destroyed,
causing stale state updates, duplicate side-effects, and memory pressure.
Flag when:
.subscribe() is called inside a component or directive without any of:
takeUntilDestroyed() (modern Angular)
take(1) / first() (valid only for single-emission streams)
- storing the result and calling
.unsubscribe() in ngOnDestroy
- the
async pipe (which auto-unsubscribes)
- A
Subject or BehaviorSubject is used as a takeUntil notifier instead of takeUntilDestroyed().
shareReplay() is called without { refCount: true } on a stream that could stay alive after
all subscribers unsubscribe (this keeps the HTTP connection and internal state alive).
Race Conditions — Wrong Flattening Operator
Choosing the wrong higher-order mapping operator (switchMap / mergeMap / concatMap / exhaustMap)
can cause stale responses to overwrite fresh ones, or duplicate side-effects.
Flag when:
mergeMap (or flatMap) is used to call an API that should cancel the previous request when a
new one comes in (e.g., a search-as-you-type, a page navigation). Should be switchMap.
switchMap is used for a write operation (POST, PUT, DELETE) where all calls must complete in
order. Should be concatMap.
mergeMap is used for a form-submit action where the user must not be able to trigger two
simultaneous requests. Should be exhaustMap.
Silent Error Swallowing
Errors that are swallowed without logging or user notification make bugs invisible in production.
Flag when:
catchError returns EMPTY or of([]) with no console.error, no toast, and no Sentry/logging call.
- A
try/catch block has an empty catch body or only a comment.
- A Promise rejection is not handled (
.then(fn) without .catch(fn) and without await in a
try/catch).
Angular Change Detection Bugs
OnPush components only re-render when their inputs change, a signal they read changes, or
markForCheck() / detectChanges() is called. Forgetting this causes stale UI.
Flag when:
- An OnPush component holds a mutable object reference and mutates it in-place
(e.g.,
this.items.push(x) instead of this.items = [...this.items, x]). The template will
not re-render because the reference did not change.
- A callback from a third-party library (e.g., a WebSocket message, a non-Angular event) modifies
component state without running inside Angular's zone or calling
markForCheck().
ChangeDetectorRef.detectChanges() is called inside ngOnDestroy or after the component is
destroyed (throws a ViewDestroyedError at runtime).
Null / Undefined Dereference
Flag when:
- A property is accessed on a value that the types say can be
null | undefined, without a
preceding null check or the optional-chaining operator (?.).
- A non-null assertion
! is used on a value that is observably nullable at that point in the
control flow (e.g., this.currentUser!.id where currentUser starts as null).
Array.prototype methods such as .map, .filter, .find are called on a value that could
be undefined at runtime (e.g., a signal or BehaviorSubject whose initial value is undefined).
- A
@ViewChild / @ContentChild result is used outside ngAfterViewInit / ngAfterContentInit
without a null guard (it is undefined before those lifecycle hooks).
Signal Bugs (Angular Signals)
Flag when:
- A
computed() call contains a side-effect (computed(() => { this.service.fetch(); return x; })).
Computed signals must be pure; side-effects won't run reliably and can cause infinite loops.
- An
effect() writes to a signal it also reads, creating a circular update cycle, unless
allowSignalWrites is intentionally set and the cycle is bounded.
toSignal() is called outside an injection context (e.g., inside ngOnInit or a method) without
passing { injector }. Throws a runtime error.
- A signal's value is compared with
=== against an object/array literal inline
(e.g., if (mySignal() === [])) — always false; likely a bug.
Form Bugs (Angular Reactive Forms)
Flag when:
formGroup.value is used where the full model is needed but some controls are disabled —
disabled controls are excluded from .value. Use formGroup.getRawValue() instead.
- A
FormControl is initialised with null but its type annotation doesn't include null
(strict mode will flag this; non-strict silently allows it, causing runtime type errors downstream).
form.reset() is called without arguments on a form that has required validators — this sets all
controls to null, which immediately makes the form invalid, which may cause unintended behaviour
if the submit button state depends on form.valid.
Router / Navigation Bugs
Flag when:
router.navigate(['../sibling']) is called from a service (not a component) without providing
a relativeTo ActivatedRoute — relative navigation from a service always fails silently.
router.navigateByUrl is used with a relative URL (not starting with /) — this resolves
against the current URL unexpectedly.
- A route guard returns
Observable<boolean> from a stream that never completes — the navigation
hangs indefinitely.
Async / Promise Bugs
Flag when:
async function result is not awaited and the error path is therefore unhandled.
- A
Promise.all([...]) call is missing await, so the promise is discarded.
setTimeout / setInterval is used inside an Angular component without being cleared in
ngOnDestroy (memory leak + stale callbacks).
lastValueFrom / firstValueFrom is called on an Observable that never emits, causing the
promise to hang or reject with an EmptyError.
Type Coercion / Comparison Bugs
Flag when:
== is used instead of === in a context where type coercion could cause false positives
(e.g., 0 == '' is true).
- A numeric template binding receives a string from
<input> without parseInt/parseFloat
(HTML inputs always return strings; arithmetic with them silently concatenates instead of adding).
- A
switch statement on a union type is missing a case branch for one of the union members
and has no default that throws.
Output Format
Group findings by file. Within a file, list findings in the order they appear in the code.
Each finding is one line:
**[SEVERITY]** `path/to/file.ts` (line ~N) — what the bug is and why it breaks. Suggested fix.
Severity labels (choose one):
- CRASH — will throw at runtime or cause an irrecoverable error under normal usage
- BUG — incorrect behaviour that users will observe, but the app keeps running
- LEAK — resource/memory leak that degrades the app over time
- RACE — race condition that is intermittent and hard to reproduce
Only use these four labels. Do not use WARN, STYLE, or INFO — this skill is bugs-only.
Finish with: N bugs found (X CRASH, Y BUG, Z LEAK, W RACE) across M files.
If nothing is found, output: No bugs found.
What NOT to flag
- Style issues, naming conventions, or architectural preferences.
- Pre-existing code in unchanged lines (stay in scope of the diff unless a bug only manifests
because of the interaction between old and new code).
- Intentional patterns documented in a nearby comment or in the file's AGENTS.md.
- TypeScript type errors that the compiler already catches — only flag runtime gaps the
compiler misses (e.g.,
any casting that hides a null).
- Hypothetical bugs that require highly unlikely input combinations without evidence in the code
that such inputs are possible.