feat: Add Linear action item syncing for incidents #139
3 issues
code-review: Found 3 issues (1 medium, 2 low)
Medium
Race condition between get_or_create and Linear API call can leave orphaned ExternalLink - `src/firetower/incidents/hooks.py:449-454`
The function uses get_or_create to claim the ExternalLink row, but if the LinearService().create_issue call raises an exception or returns None, the link is deleted. However, between the get_or_create and the delete, a concurrent request could see the placeholder link with empty url and incorrectly assume Linear is already configured. This is a side-effect/concurrency concern in incident creation flow.
Low
incident.save(update_fields=["linear_parent_issue_id"]) may overwrite concurrent updates - `src/firetower/incidents/hooks.py:478-479`
Saving the incident with update_fields after a network call to Linear bypasses any optimistic concurrency. If another process modified the incident concurrently (e.g., title updates triggering Linear sync described in the PR), only linear_parent_issue_id is persisted, but the in-memory incident may have stale data for other fields. While update_fields limits the columns written, the incident object used elsewhere in on_incident_created is not refreshed, which could cause downstream hooks to operate on stale data.
LinearService instantiated per hook call - `src/firetower/incidents/hooks.py:759`
Both on_title_changed and on_visibility_changed instantiate a new LinearService() on every invocation. If the constructor performs OAuth token acquisition or other expensive setup, this could create unnecessary overhead and additional API calls per title/visibility change. Consider reusing a module-level service or caching the auth token.
Duration: 1m 25s · Tokens: 453.0k in / 2.2k out · Cost: $2.51 (+merge: $0.00)
Annotations
Check warning on line 454 in src/firetower/incidents/hooks.py
sentry-warden / warden: code-review
Race condition between get_or_create and Linear API call can leave orphaned ExternalLink
The function uses get_or_create to claim the ExternalLink row, but if the LinearService().create_issue call raises an exception or returns None, the link is deleted. However, between the get_or_create and the delete, a concurrent request could see the placeholder link with empty url and incorrectly assume Linear is already configured. This is a side-effect/concurrency concern in incident creation flow.