@@ -14,8 +14,6 @@ permissions:
1414jobs :
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
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