Skip to content

Commit 286ea22

Browse files
committed
ci: harden Linear sync (error handling + rerun idempotency)
1 parent 3973a68 commit 286ea22

1 file changed

Lines changed: 34 additions & 14 deletions

File tree

.github/workflows/linear-sync.yml

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ permissions:
1414
jobs:
1515
create-linear-card:
1616
runs-on: ubuntu-latest
17-
# Skip if the API key isn't configured yet, so forks / unconfigured repos
18-
# don't produce noisy failing runs.
1917
if: ${{ github.event.issue != null }}
2018
steps:
2119
- name: Create Linear card
@@ -39,6 +37,14 @@ jobs:
3937
exit 0
4038
fi
4139
40+
# Idempotency: if this issue already has a Linear backlink comment
41+
# (e.g. from a manual job re-run), do nothing.
42+
if gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json comments \
43+
--jq '.comments[].body' 2>/dev/null | grep -q "Synced to Linear:"; then
44+
echo "Issue already synced to Linear (marker found); skipping."
45+
exit 0
46+
fi
47+
4248
# Build the Linear description (Markdown). jq handles all escaping.
4349
description=$(jq -n \
4450
--arg body "${ISSUE_BODY:-_No description provided._}" \
@@ -59,23 +65,37 @@ jobs:
5965
variables: { input: { teamId: $teamId, stateId: $stateId, title: $title, description: $description } }
6066
}')
6167
62-
response=$(curl -sS -X POST https://api.linear.app/graphql \
68+
# Call Linear. Retry transient failures; fail loudly on HTTP errors.
69+
# (Linear returns HTTP 200 even for GraphQL errors, so we also inspect .errors.)
70+
http_code=$(curl -sS -o response.json -w '%{http_code}' \
71+
--retry 3 --retry-all-errors --connect-timeout 10 --max-time 30 \
72+
-X POST https://api.linear.app/graphql \
6373
-H "Authorization: $LINEAR_API_KEY" \
6474
-H "Content-Type: application/json" \
65-
--data "$payload")
75+
--data "$payload") || { echo "::error::Could not reach the Linear API."; exit 1; }
6676
67-
echo "Linear API response: $response"
77+
response=$(cat response.json)
78+
echo "Linear HTTP $http_code"
6879
69-
success=$(echo "$response" | jq -r '.data.issueCreate.success // false')
70-
if [ "$success" != "true" ]; then
71-
echo "::error::Linear card creation failed. Response: $response"
72-
exit 1
80+
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
81+
echo "::error::Linear returned HTTP $http_code: $response"; exit 1
82+
fi
83+
if echo "$response" | jq -e 'has("errors")' >/dev/null 2>&1; then
84+
echo "::error::Linear GraphQL errors: $(echo "$response" | jq -c '.errors')"; exit 1
85+
fi
86+
if [ "$(echo "$response" | jq -r '.data.issueCreate.success // false')" != "true" ]; then
87+
echo "::error::Linear issueCreate did not succeed: $response"; exit 1
7388
fi
7489
75-
linear_url=$(echo "$response" | jq -r '.data.issueCreate.issue.url')
76-
linear_id=$(echo "$response" | jq -r '.data.issueCreate.issue.identifier')
90+
linear_url=$(echo "$response" | jq -er '.data.issueCreate.issue.url') \
91+
|| { echo "::error::Linear response missing issue url: $response"; exit 1; }
92+
linear_id=$(echo "$response" | jq -er '.data.issueCreate.issue.identifier') \
93+
|| { echo "::error::Linear response missing issue identifier: $response"; exit 1; }
7794
echo "Created Linear card $linear_id -> $linear_url"
7895
79-
# Link back on the GitHub issue so both sides are cross-referenced.
80-
gh issue comment "$ISSUE_NUMBER" --repo "$REPO" \
81-
--body "🔗 Synced to Linear: [$linear_id]($linear_url)"
96+
# Link back on the GitHub issue. A failure here must NOT orphan the
97+
# card silently: surface the URL so it can be recovered.
98+
if ! gh issue comment "$ISSUE_NUMBER" --repo "$REPO" \
99+
--body "🔗 Synced to Linear: [$linear_id]($linear_url)"; then
100+
echo "::warning::Linear card $linear_id ($linear_url) was created, but posting the backlink comment failed."
101+
fi

0 commit comments

Comments
 (0)