feat: Add Linear action item syncing for incidents #139
8 issues
High
update_or_create can steal ActionItems from another incident - `src/firetower/incidents/services.py:274-285`
ActionItem.objects.update_or_create(linear_issue_id=issue["id"], defaults={"incident": incident, ...}) keys solely on linear_issue_id. If the same Linear issue is referenced as a related issue from a different incident's parent (or was previously associated with another incident), this sync will silently reassign the ActionItem to the current incident, mutating another incident's data. The subsequent delete step then removes ActionItems no longer matching by linear_issue_id, but the cross-incident hijack already occurred. This violates data isolation between incidents.
parse_incident_id uses re module without importing it - `src/firetower/incidents/views.py:67-69`
The new parse_incident_id function calls re.escape and re.match, but the import block shown does not include import re. If re is not imported elsewhere in the file, any call to parse_incident_id will raise NameError at runtime, breaking incident ID parsing for endpoints that depend on it.
Also found at:
src/firetower/integrations/services/linear.py:142-145
Medium
Module-level LinearService singleton retains stale workflow state cache across incidents - `src/firetower/incidents/services.py:232`
_get_linear_service() returns a process-wide singleton whose _workflow_states_cache is populated on first call and never invalidated. If a Linear admin renames or adds workflow states, every Firetower process will keep using stale state IDs (potentially writing to a nonexistent state) until the worker is restarted. This is the service used in sync_action_items_from_linear at line 232.
Also found at:
src/firetower/incidents/services.py:198-209
GET request triggers external Linear sync as a side effect - `src/firetower/incidents/views.py:343-351`
ActionItemListView.list() calls sync_action_items_from_linear(incident) on every GET request. Performing write/sync side effects on a GET violates HTTP semantics and can cause unintended behavioral changes (extra Linear API calls, DB writes) whenever clients, crawlers, or retries hit the endpoint. Although described as throttled, the sync is still invoked synchronously inside the request path, which can also slow down list responses and hide Linear outages from users when failures are only logged.
Also found at:
src/firetower/incidents/services.py:240-251
Potential N+1 on assignee userprofile in ActionItemSerializer - `src/firetower/incidents/views.py:332-333`
get_queryset only select_related's assignee__userprofile. If ActionItemSerializer references other related fields (e.g. created_by, linear metadata, or many-to-many relations), each serialized item will issue extra queries, producing an N+1 pattern on the list endpoint. Consider adding the necessary select_related/prefetch_related based on the serializer's fields.
Also found at:
src/firetower/incidents/services.py:174-179
Token refresh has a TOCTOU race that can clobber concurrent tokens - `src/firetower/integrations/services/linear.py:73-78`
_request_new_token deletes all LinearOAuthToken rows and creates a new one inside transaction.atomic() without row-level locking. If two requests hit _get_access_token near expiry, both will call Linear's token endpoint and race to delete/insert, potentially invalidating a token another in-flight request just stored, and producing redundant token requests against Linear (rate-limit risk). Use select_for_update() or a database-level lock / cache lock so only one worker refreshes at a time.
Also found at:
src/firetower/integrations/services/linear.py:125-135
Low
Broad except may mask programming errors and leak as 500 - `src/firetower/incidents/views.py:372-390`
SyncActionItemsView.post catches bare Exception around sync_action_items_from_linear and returns a generic 500. This will swallow non-Linear errors (e.g. TypeError, permission/programming bugs) and report them as Linear failures, making debugging harder. Consider catching the specific Linear/integration exceptions raised by the service and letting unexpected errors propagate to Django's error handling.
ActionItemListView triggers Linear sync on every GET without throttling at view layer - `src/firetower/incidents/views.py:348-354`
The list() method calls sync_action_items_from_linear(incident) on every GET request. While the description claims throttling exists in the service layer, any unauthenticated-but-permitted user with access can repeatedly hit this endpoint to trigger external Linear API calls. If the service-level throttle is per-incident rather than per-user/IP, this could be abused for resource exhaustion or to amplify load on the Linear API. The broad except Exception swallows all errors silently, which can mask repeated failures.
Also found at:
src/firetower/incidents/views.py:380-396
5 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| security-review | 0 | 72.6s | $2.54 |
| django-access-review | 0 | 55.0s | $2.66 |
| django-perf-review | 0 | 175.0s | $2.14 |
| code-review | 4 | 162.0s | $2.78 |
| find-bugs | 4 | 122.0s | $2.74 |
Duration: 586.5s · Tokens: 2.6M in / 14.8k out · Cost: $12.87 (+merge: $0.01, +dedup: $0.00)