Post daily new issues
Skill warpdotdev/client-release-agent-oss/.warp/skills/post-daily-new-issues
Warp agent skills for release engineering automation — cherry-picking fixes, cutting release candidates, tracking PR status, and posting Sentry/Slack digests.
npx -y skills add warpdotdev/client-release-agent-oss --skill post-daily-new-issuesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 29 days oldThe repository was created 29 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Post the daily digest of new Sentry issues for the current stable release to an on-call Slack channel. Use when the user asks to run or post the daily stable Sentry digest for on-call.
SKILL.md
11.5 KB, ~3.1k tokens by cl100k_base, as published. Nobody here has run it
post-daily-new-issues
Post a daily digest of new Sentry issues for the current release on $DIGEST_CHANNEL (default stable) to an on-call Slack channel, tagging the on-call usergroup. The digest is scoped to a single calendar date so a scheduled agent can run this once per weekday without re-reporting issues that were already shared on a previous day.
Configuration
This skill reads environment-specific values from environment variables (see the repo README.md):
SLACK_BOT_TOKEN— Slack bot token withchannels:read,chat:write,usergroups:readscopes.SENTRY_AUTH_TOKEN— Sentry auth token withevent:readandproject:readscopes. Used to call the Sentry REST API directly.SENTRY_ORG— Sentry org slug.SENTRY_PROJECT— Sentry project slug (e.g. your release client project).SENTRY_PROJECT_ID— numeric Sentry project ID (used to build release URLs).RELEASE_SLACK_CHANNEL— Slack channel name (or ID) to post the digest in.ONCALL_SLACK_GROUP— Slack usergroup handle for the on-call (e.g.oncall-primary).RELEASE_VERSIONS_URL— optional public URL returning a JSON document with the current release version. Omit if you pass a version explicitly.DIGEST_CHANNEL— channel whose release the digest reports on (defaultstable); also selects the version key read fromRELEASE_VERSIONS_URL.
Step 0: Decide whether to run today (weekend skip)
This skill is intentionally a no-op on Saturday and Sunday. Compute the day of week in UTC:
DOW=$(date -u +%u) # 1=Mon ... 7=Sun
- If
DOWis6(Sat) or7(Sun): print a short note (e.g."Skipping: weekend") and exit without posting to Slack. - Otherwise continue.
If the caller passed an explicit target date (see Step 2), skip this step entirely and use what they provided.
Step 1: Find the current release version
If RELEASE_VERSIONS_URL is set, read the current version for $DIGEST_CHANNEL from it. The exact JSON path depends on your release metadata format; the channel key is selected by $DIGEST_CHANNEL, for example:
RELEASE_VERSION=$(curl -fsS "$RELEASE_VERSIONS_URL" | jq -r --arg ch "$DIGEST_CHANNEL" '.[$ch].version')
RELEASE_VERSION looks like v0.YYYY.MM.DD.HH.MM.stable_NN (a documented example template — adjust to match the version scheme your release pipeline emits). If RELEASE_VERSIONS_URL is unset, ask the user for the current release version. If the field is missing or curl fails, stop and surface the error — do not post a partial digest.
Step 2: Compute the target date window
The window is a half-open UTC interval [START_DATE, END_DATE) (i.e. firstSeen >= START_DATE AND firstSeen < END_DATE). Both are YYYY-MM-DD strings.
The default behavior depends on which weekday the agent is running:
- Tue–Fri (
DOW2–5): report yesterday only.START_DATE= yesterday (UTC),END_DATE= today (UTC).
- Mon (
DOW1): report Saturday + Sunday + the previous Monday's overnight, because the agent did not run on the weekend.START_DATE= 3 days ago (UTC, that Saturday),END_DATE= today (UTC, that Monday).
On macOS (BSD date):
TODAY=$(date -u +%Y-%m-%d)
if [ "$DOW" = "1" ]; then
START_DATE=$(date -u -v-3d +%Y-%m-%d) # Saturday
else
START_DATE=$(date -u -v-1d +%Y-%m-%d) # Yesterday
fi
END_DATE=$TODAY
On Linux (GNU date, e.g. inside a remote environment):
TODAY=$(date -u +%Y-%m-%d)
if [ "$DOW" = "1" ]; then
START_DATE=$(date -u -d '3 days ago' +%Y-%m-%d)
else
START_DATE=$(date -u -d 'yesterday' +%Y-%m-%d)
fi
END_DATE=$TODAY
Override: if the user explicitly asks for a different window (e.g. "post the digest for 2026-04-25"), use their dates instead and skip the weekend check.
Step 3: Fetch new issues from Sentry
Call the Sentry REST API directly with $SENTRY_AUTH_TOKEN. The query string must include both:
firstRelease:<RELEASE_VERSION>— restricts to issues whose first-seen release matches the live release build (this matches Sentry's "New Issues" tab on the release page).firstSeen:>=START_DATE firstSeen:<END_DATE— narrows to the target window so already-reported issues from previous days are excluded. This is the noise-control mechanism; do not omit it.
Sort by frequency (sort=freq) and request limit=100 so the true total and the top 3 are both stable even if there's a long tail of one-event issues.
QUERY="firstRelease:${RELEASE_VERSION} firstSeen:>=${START_DATE} firstSeen:<${END_DATE}"
ISSUES_JSON=$(curl -fsS -G "https://sentry.io/api/0/projects/${SENTRY_ORG}/${SENTRY_PROJECT}/issues/" \
-H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
--data-urlencode "query=$QUERY" \
--data-urlencode "sort=freq" \
--data-urlencode "limit=100")
The response is a JSON array. Each element has at least: shortId, title, count (event count, as a string), userCount, firstSeen, lastSeen, permalink. Parse it with jq.
If the array is empty, exit silently — do NOT post anything to Slack. Print a short note like "No new issues in window; skipping post" so the run log makes the no-op visible, then stop. The on-call channel should only get a message when there's something to act on; an empty digest just adds noise.
Step 4: Compute the total and pick the top 3
The message has two distinct numbers, and they must come from different slices of the response:
TOTAL_NEW_ISSUES— the full count of new issues in the window (used in the "N new issues since …" summary line). Compute this from the entire array, before any truncation.- Top 3 issues — the first 3 entries by event count, used to populate the bullet list. The list is intentionally short to keep the digest scannable; the on-call clicks through to Sentry for the long tail.
For each of the top 3, capture:
- Short ID from
.shortId - Title from
.title— truncate to ~140 chars on a single line - Event count from
.count(cast to int) - User count from
.userCount - Issue URL: prefer
.permalinkif present, else build it ashttps://${SENTRY_ORG}.sentry.io/issues/<SHORT_ID>
If fewer than 3 issues exist, include all of them.
Example jq to compute both numbers in one pass:
TOTAL_NEW_ISSUES=$(printf '%s' "$ISSUES_JSON" | jq 'length')
BULLETS=$(printf '%s' "$ISSUES_JSON" | jq -r --arg org "$SENTRY_ORG" '
[.[] | {
short: .shortId,
title: (.title | gsub("\n"; " ") | .[0:140]),
events: (.count | tonumber),
users: .userCount,
url: (.permalink // ("https://" + $org + ".sentry.io/issues/" + .shortId))
}]
| sort_by(-.events)
| .[0:3]
| map("• <\(.url)|\(.short)> — \(.events) events, \(.users) users — \(.title)")
| join("\n")
')
Step 5: Resolve Slack IDs
Resolve once per run. If the channel ID and subteam ID are stable in your environment you may cache them, but always validate they still match.
# Channel ID for $RELEASE_SLACK_CHANNEL
CHANNEL_ID=$(curl -s -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
"https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=1000" | \
jq -r --arg name "$RELEASE_SLACK_CHANNEL" '.channels[] | select(.name==$name) | .id')
# Subteam ID for the on-call usergroup
SUBTEAM_ID=$(curl -s -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
"https://slack.com/api/usergroups.list" | \
jq -r --arg handle "$ONCALL_SLACK_GROUP" '.usergroups[] | select(.handle==$handle) | .id')
If either lookup is empty, surface the error and stop — do not guess IDs.
Step 6: Format and post the message
Use Slack Block Kit. The mention must be <!subteam^${SUBTEAM_ID}> so it actually pings the on-call group. Build a text fallback for notifications and search.
Build the Sentry release link first — every message must include it so the on-call can jump straight into the filtered "new issues" view:
RELEASE_URL="https://${SENTRY_ORG}.sentry.io/explore/releases/${RELEASE_VERSION}/?issuesType=new&project=${SENTRY_PROJECT_ID}"
The issuesType=new query param is what pre-selects the New Issues tab; do not omit it. project=${SENTRY_PROJECT_ID} is the numeric ID for your Sentry project.
Message structure:
- Header:
<RELEASE_VERSION>: new Sentry issues for <date or range> - Section with the subteam mention and a one-line summary that uses the true total, not the bullet count. Example:
<!subteam^S012345> — ${TOTAL_NEW_ISSUES} new issues since <START_DATE> · <RELEASE_URL|View in Sentry>On a Monday run, phrase the window assince Saturdayinstead of a bare date. - Section listing only the top 3 issues by event count, one per line, in this format:
• <ISSUE_URL|SHORT_ID> — <events> events, <users> users — <title>
- Section with a triage reminder, e.g.:
_Please <${RELEASE_URL}|review the full New Issues tab in Sentry> and triage._This is what tells the on-call the bullets are only a sample and that they need to click through.
Use Slack mrkdwn link syntax (<URL|text>) inside mrkdwn blocks. Keep titles on a single line; truncate with … if needed.
If TOTAL_NEW_ISSUES is zero, do not reach this step — the skill exits in Step 3 without posting. There is no "all clear" message; silence is the all-clear signal.
Date display:
- Tue–Fri: show
START_DATE(e.g.2026-04-28 (UTC)). - Mon: show the range, e.g.
Sat 2026-04-25 – Mon 2026-04-27 (UTC).
Post as a top-level message (no thread_ts). Suppress link previews so the message stays compact — with several Sentry links per post, unfurls would dominate the channel:
curl -s -X POST \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
-H "Content-Type: application/json" \
--data "$(jq -n \
--arg channel "$CHANNEL_ID" \
--arg text "$TEXT_FALLBACK" \
--argjson blocks "$BLOCKS_JSON" \
'{channel: $channel, text: $text, blocks: $blocks, unfurl_links: false, unfurl_media: false}')" \
"https://slack.com/api/chat.postMessage" | jq '{ok, ts, error}'
unfurl_links: false blocks regular URL previews; unfurl_media: false blocks image/video previews. Both are required — omitting either lets Slack render whichever class of preview it didn't see suppressed.
If ok is false, log the error field and stop. Common failures: not_in_channel (invite the bot), missing_scope, channel_not_found.
Step 7: Confirm
Fetch and print the permalink so a human (or downstream agent) can click into the post:
curl -s -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
"https://slack.com/api/chat.getPermalink?channel=$CHANNEL_ID&message_ts=<MESSAGE_TS>" | \
jq -r '.permalink'
Notes
- Why scope by
firstSeenand not justfirstRelease? Without the date filter, every daily run would re-post the same long-lived issues from earlier in the release's lifetime. Filtering byfirstSeenon the target date is what makes the digest actionable and not noisy. - Why bundle the weekend on Monday instead of running on Sat/Sun? If the on-call rotation is staffed Mon–Fri, weekend posts would go unread. Bundling on Monday surfaces anything that broke over the weekend in a single message.
- Manual reruns: if the user provides explicit
START_DATE/END_DATE(e.g. for a backfill or to retry a missed day), use those and skip the weekday/weekend logic. - A reusable helper that fetches and normalizes issues is provided at
scripts/fetch_new_issues.sh.