Skip to content

feat(notify): redesign Slack payload, add hash / duration / url - #18

Merged
gamechanger1s merged 19 commits into
mainfrom
feat/slack-notify
Jul 17, 2026
Merged

feat(notify): redesign Slack payload, add hash / duration / url #18
gamechanger1s merged 19 commits into
mainfrom
feat/slack-notify

Conversation

@gamechanger1s

@gamechanger1s gamechanger1s commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Redesigns the actions/notify Slack card payload. Same composite-action interface plus one additive input (deployed-url); expanded card layout.

New/changed fields on the card:

  • Header now shows {icon} {label} {version} (icon = ✅ / ❌).
  • Body rearranged into a single-column key/value grid: COMMIT, PR, AUTHOR, HASH, DURATION, DATE, plus URL on success or FAILED AT (top row) on failure.
  • Footer: View run button linking back to the CI run.

New auto-fetched data (no caller wiring needed):

  • DURATION — pulled from /repos/{repo}/actions/runs/{run_id} (run_started_at), formatted as Xm Ys.
  • FAILED AT — pulled from /repos/{repo}/actions/runs/{run_id}/jobs, first job with conclusion == "failure".

Both use the existing github-token. Callers must add actions: read to the notify job's permissions: block; if missing, the action emits a workflow ::warning:: and the two rows drop out (loud fail instead of silent drop).

One new input: deployed-url — full URL of the deployed app. Rendered on success only; display strips protocol + trailing slash, link points at the full URL. Caller resolves per-environment (e.g. ${{ github.ref_name == 'main' && 'https://prod.example.com' || 'https://dev.example.com' }}).

Author display: now sources .commit.author.name (git metadata) before falling back to .author.login.

Version bump: ships as v4.0.0 + @v4 moving tag. Semver-wise the input schema is additive, but the visible card surface changes materially, so a clean major is the honest signal. No current consumer pins @v3 — every rollout is an explicit bump PR that also adds actions: read.

Alert-card path preserved from #20

actions/enforce (composite empty-PR-body gate, shipped in #20) uses header / color / status inputs on notify to render an attachments-wrapper alert card. This PR keeps that path intact as an alert-mode branch: when any of those inputs is set, the redesigned deploy card is swapped for the pre-v4 attachments-wrapper alert card (byte-identical). Run/jobs fetch is skipped on that path — DURATION and FAILED AT are deploy-only.

Not in this PR

  • No tag cut — a downstream service canary validates @feat/slack-notify end-to-end before v4.0.0 is tagged and @v4 is moved.
  • No consumer bumps beyond the canary. Each existing v3.x pin becomes a separate follow-up PR (bump pin + add actions: read + add deployed-url where the caller wants it).
  • No CHANGELOG.md — repo doesn't have one; release notes live in the eventual GitHub Release.

Testing plan

  1. Canary caller pins notify@feat/slack-notify and sets dev + prod deployed-url values.
  2. Push a commit; capture the dev-channel Slack card (success path).
  3. Reproduce a failure (broken build) to capture the failure card with FAILED AT.
  4. Once both paths render as intended, cut v4.0.0, move @v4, squash-merge this PR.

Test plan

  • Canary success card verified (validated at 772fc1a before rebase; re-verify post-rebase).
  • Canary failure card verified (validated at 772fc1a before rebase; re-verify post-rebase).
  • Alert-mode path unchanged from feat(empty-pr-guard): composite action + notify alert-card inputs #20 (no consumer of actions/enforce today; verified by construction — attachments payload byte-identical to main).
  • Duration + hash + author + URL render as designed.
  • ::warning:: fires when a caller omits actions: read.
  • Tag v4.0.0; move @v4; squash-merge this PR.

Refactor the notify composite action to render a single-column key/value
grid instead of the wide two-column layout. Adds three new fields:

* HASH     — linked short SHA (7 chars)
* DURATION — pipeline elapsed time, auto-fetched from the run API
* URL      — deployed app URL (success only), display strips protocol

On failure a red-dot 'FAILED AT' row is promoted to the top, populated
with the first failing step name (auto-fetched from the run's jobs API).

Card structure:

  header    — icon + label + version
  context   — 'Deploy succeeded' / 'Deploy failed' subtitle
  section   — key/value fields (split into two blocks; Slack caps at 10)
  actions   — 'View run' button in the footer

Duration and failed-step are auto-resolved by the action, so callers only
need to grant `actions: read` on the notify job. No new caller wiring
beyond the one new `deployed-url` input.

Refs AGODEV-1495. Not tagged yet — canary-tested via commerce-backend
against this branch before v3.4.0 is cut and @V3 is moved.
…stay on one line

Slack section.fields is a 2-col grid that wraps values past ~45 chars per
column, which produced ugly multi-line COMMIT/PR rows on the v3.4.0 canary
card. Move both out of the field grid into a dedicated full-width mrkdwn
section above it; the grid keeps the short tabular rows (AUTHOR, HASH,
DURATION, DATE, URL / FAILED AT).
… and PR

Without -r, jq JSON-encodes the joined string; the literal \n survives all
the way into the Slack payload where it renders as the two-char sequence
instead of a line break. -r gives raw output; jq re-encodes correctly when
the second jq call rebuilds the payload.
…AILED AT

Four fixes on the v3.4.0 canary card, all applied together to avoid extra
churn on the feat/slack-notify canary:

1. FAILED AT now shows the failed JOB name ("Build", "Deploy", …), not a
   step name. Inner steps of composite actions are opaque to the /jobs
   endpoint (only the outer `Run owner/repo/actions/name@ref` wrapper is
   visible), so step-level names were misleading in practice.
2. Drop the "Deploy failed" / "Deploy succeeded" subtitle. The header
   emoji already conveys status.
3. Fold COMMIT and PR back into the same 2-col field grid as the other
   rows, so all seven fields share a single uniform label/value block.
   Long commit subjects will wrap; consistency preferred here.
4. Drop the 🔴 prefix from FAILED AT — the header emoji already
   marks the card as failed.
Replace the section.fields 2-col grid (which capped values at ~30 chars per
column and wrapped long commit subjects / PR refs) with a single
section.text mrkdwn block. Labels are wrapped in inline code (`) so they
render in monospace, then right-padded to 9 chars ("FAILED AT" is the
longest). Result: labels line up in a fixed left column, values get the
full remaining card width, long commit subjects and PR refs no longer wrap.
`label` is a jq reserved keyword (used with `label $out | break`), so
`def label(k): ...` was a compile error. Rename to `padlabel`.
…cing

Slack collapses runs of ASCII spaces in mrkdwn to a single space, so the
plain-bold + space-padding attempt (1f978e4) lost its alignment. Non-
breaking spaces (U+00A0) are preserved individually; Slack still uses a
variable-width font so alignment is approximate (bold letter width > NBSP
width), but the value column drift is small and consistent. Bumped the
padding target from 9 to 12 chars to compensate for NBSP being narrower
than a bold letter.
The previous commit put literal U+00A0 bytes in the composite-action's
run: block. GH Actions rendered the run: value to a temp .sh file and
bash choked on it ("syntax error near unexpected token '('", line 110)
- something in the render pipeline mis-tokenises literal NBSPs even when
they sit inside a jq single-quoted expression. Emit the NBSPs from jq's
\u00a0 escape instead so the action.yml source stays pure ASCII; the
final Slack payload still contains real NBSPs for the column padding.
…lock

Slacks (formerly Slacks apostrophe-s) in a comment inside the jq  argument
closed the outer bash single quote, so everything after it was parsed as
shell and hit a syntax error at the padlabel line. Rephrase the comment
apostrophe-free and add a NOTE explaining the shell-quoting gotcha.
…ld letter width

NBSP (\u00a0) padding rendered narrower than bold letters in Slacks Lato font,
so labels with more bold chars (COMMIT, AUTHOR) drifted right of short-label
rows (PR, URL). Em space (\u2003) is defined as the fonts em width, which is
roughly the width of a bold letter. That makes total label+padding width
= approximately em_width * 12 for every row, giving consistent column alignment.
Two fixes on the v3.4.0 canary card:

1. DURATION value drifted slightly left of the PR/AUTHOR/HASH/DATE/URL
   column. Root cause: em-space (1 em) is wider than a bold letter
   (~0.75 em in Slacks Lato), so a longer label paired with fewer
   em-spaces of padding produced a NARROWER total width. Pure em-space
   padding cannot align rows when letter width and space width diverge.
   Fix: keep the em-space padding (pad to 12 chars total) and add N
   NBSPs per label where N = label length. NBSP is ~0.25 em, so the
   NBSP tail compensates for the 0.25 em per-letter shortfall. Total
   width = L*0.75 + (12-L)*1 + L*0.25 = 12 em, constant across labels.
   DURATION (8 chars) now gets the largest NBSP tail (8), PR (2 chars)
   the smallest (2).

2. DATE format: dots to dashes. `2026.07.15` reads as a version number;
   ISO `2026-07-15` is the recognised date format.
Change the row join separator from `\n` to `\n\n` so every field is
visually separated by one blank line (COMMIT / PR / AUTHOR / HASH /
DURATION / DATE / URL). Denser vertical rhythm makes long commit
subjects easier to distinguish from the row below when the value wraps.

No structural change: header, actions block, and field order all
untouched.
Post-dispatch screenshot of 4186ffe showed COMMIT, AUTHOR, and DURATION
values drifting right of the column formed by PR / HASH / DATE / URL /
FAILED AT. Root cause is that Slack Lato bold letter width varies per
character: the three drifted labels are all-caps single words dominated
by wide letters (M / D / U / R / A / O / N), so under the L*0.75 em
approximation they render wider than the formula expects and the padded
row overshoots the reference column.

Fix: hard-code a 2-NBSP reduction on those three labels only. Others
untouched. This is empirical tuning, not a general formula, and may need
another iteration of +/- 1 NBSP after the next screenshot.

Padding counts after this commit:
  COMMIT    6 em + 4 NBSP  (was 6 + 6)
  AUTHOR    6 em + 4 NBSP  (was 6 + 6)
  DURATION  4 em + 6 NBSP  (was 4 + 8)
  everything else unchanged
Post-shave dispatch of 81e85ee confirmed COMMIT / AUTHOR / HASH /
DURATION / DATE / URL all landed on the reference column. Only PR
still sat slightly left. PR is only 2 letters (P + R) and both are
medium-width, so the label ends at a narrower X than the four-letter
labels — the em-space count is already at ceiling (10) and the letter
contribution is small, leaving the row slightly under target width.

Fix: negative shave on PR only (extends the NBSP tail by 1). No other
label touched.
This reverts 08ee58b. The +1 NBSP overshot — PR now sits slightly right
of the reference column. Remove the -1 shave clause, back to PR = 10 em
+ 2 NBSP.

We do not have a fractional-width padding char that fits between 2 and
3 NBSPs (~0.25 em each), so this direction is capped. If PR's residual
drift left is still visible after this revert, the remaining options are
option 3 (monospace code block) or accepting the sub-NBSP miss.
75ab3e3 reverted the earlier PR +1 NBSP (which overshot). Post-revert
dispatch confirmed PR still sits slightly left of the reference column.
+2 NBSPs (10 em + 4 NBSP total on PR) should land closer to the target
than +1 did — the earlier +1 attempt was measured against the pre-shave
formula and may have interacted with the wide-letter-shave landing.
Nothing else touched.
Same drift and same fix as cf0c7ff for PR: the FAILED AT row (present
only on failure cards) sits slightly left of the reference column,
because the analytical formula under-estimates the width shortfall of
labels whose bold-letter content skews narrow (F, I, L, T, E + space).
Extend the shave map so FAILED AT gets the same -2 (i.e. +2 NBSP) that
PR does. FAILED AT now 3 em + 11 NBSP.

No other label touched.
@gamechanger1s gamechanger1s changed the title feat(notify): redesign Slack payload, add hash / duration / url (AGODEV-1495) feat(notify): redesign Slack payload, add hash / duration / url Jul 16, 2026

@tarpanpathak tarpanpathak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Chinmay, spent some time on this. Overall the direction is good and the schema stays backward-compat (deployed-url is additive with a "" default, no existing inputs renamed). But the card people actually see in Slack changes end-to-end, so there's real behavioral BC even though the API is clean. Handful of things to work through before this lands.

Customer-specific info in the description. The PR body references a specific downstream repo and Slack channel. This is our shared ci-workflows repo though, so let's keep customer names out of the description too. Something like "companion caller PR validates end-to-end on a downstream service before we tag v3.4.0 and move @v3" works.

Same thing in the README. The new label input description says Prefer a bare component name ("Commerce Backend"). The guidance itself is fine (channel implies environment, no need to repeat it in the label), just want the example string to be something generic like "Backend" or "Payment API".

Sentence in the PR body got truncated. "Redesigns the actions/notify Slack card payload per the mockups on. Same composite-action interface..." Looks like the mockups link got dropped. Either link them or trim that phrase.

Version bump strategy is worth an explicit call. As proposed (v3.4.0 + move @v3), every consumer currently pinned at @v3 will get the new card on their next pipeline. That's the color bar going away, header format changing from {emoji} {label} — {status} to {icon} {label} {version}, new body grid, new subtitle. Strict semver says minor is fine because the inputs are additive, but the visible surface changes materially, which is exactly the kind of thing people usually cut a major for. Two ways to go: either cut v4.0.0 and move @v4 so @v3 stays stable for whoever hasn't opted in yet, or keep v3.4.0 as planned but send an org-wide heads-up before the tag moves so nobody's caught off guard. No strong preference, just want an explicit decision on record before the tag moves.

Coordinate with #20 before merging. #20 (composite actions/enforce + notify alert-card inputs) also touches actions/notify. Two conflicts:

  • Text: it adds header / color / status inputs to notify, so both PRs edit the same file and will conflict on rebase.
  • Design: this PR removes the attachments: [{ color, blocks: [...] }] wrapper entirely and goes to top-level blocks: [...]. Fine on its own, but the alert-card use case in #20 (revert notifications from actions/enforce, needs a colored bar + custom title) has nowhere to attach the color anymore.

Cleanest path is probably to land #20 first (it's a small additive change), then rebase this on top and decide whether you want to (a) keep an attachments: [...] conditional branch for when header/color is set so the alert path still works, or (b) drop the alert path here and let actions/enforce do its own thing. Flagging so you see #20 exists before you make the call.

I'll drop a few smaller notes inline on the diff: silent-drop when actions: read isn't granted (existing @v3 callers who don't add it will lose DURATION and FAILED AT without any warning), the padding-math fragility, author display change, and date -d portability. None of those are blockers.

One more: please squash on merge. 18 commits and most of them are padding-tweak-then-revert-then-re-tweak, doesn't need to be preserved as history.

Comment thread README.md
Comment thread actions/notify/action.yml
Comment thread actions/notify/action.yml
Comment thread actions/notify/action.yml
Fold in #20 (actions/enforce + notify alert-card inputs) and address
tarpanpathak review feedback:

- Alert-mode branch in actions/notify: when header / color / status is
  set, swap the redesigned deploy card for the pre-v4 attachments-wrapper
  alert card (byte-identical). actions/enforce alert path survives v4.
  Skips run/jobs fetch on alert path (not needed there).
- Emit ::warning:: when /actions/runs or /actions/runs/{id}/jobs come
  back empty (loud fail if caller forgot actions: read; DURATION and
  FAILED AT otherwise drop silently).
- Maintenance-risk note above padlabel: Lato-empirical, drift-silent,
  fallback recipe recorded inline.
- README: enforce section restored, notify table gains header / color /
  status rows, label example changed from "Commerce Backend" to
  "Backend", @V3 example pins bumped to @v4 in preparation for the
  v4.0.0 cut.

Rollback anchor: 772fc1a (last
known-good HEAD before this merge; both cards validated by user).
@gamechanger1s

gamechanger1s commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the pass, @tarpanpathak. Decisions and plan:

Version bump: cutting v4.0.0 and moving @v4. Rationale = your read: visible surface changes materially (color bar, header format, body grid), so a clean major is the honest signal even though the input schema stays additive. Cost is nil since no repo actually pins @v3 — every current consumer pins explicit @v3.3.0 / @v3.3.1, and each will get an explicit bump PR. v3.4.0 is also already claimed by #20, so this was the right forward path either way.

Reconciling with #20. Rebasing on main and keeping the attachments: [...] conditional branch when header/color/status is set — the redesigned top-level blocks: card renders on the default deploy path only. actions/enforce's alert-card use case survives byte-for-byte. No consumers of enforce today (I grepped all 4 orgs), but v4 is a public major and the reconciliation is cheap.

PR body + README cleanup: dropping the customer-specific repo/channel mentions, fixing the truncated "per the mockups on." line, changing the label example to "Backend".

Silent-drop on missing actions: read: adding a ::warning:: from the action when runs/{id} or jobs come back empty so it fails loud instead of silently dropping DURATION / FAILED AT. Each v4 rollout PR will also add actions: read to the notify job as part of the bump.

Padding fragility: adding a maintenance-risk comment above padlabel(k) calling out the Lato-specific tuning and the shave map. Full fallback-rendering escape hatch = separate follow-up if we ever want it.

Deferred: date -u -d portability — noted, not touching unless we ever need macOS runners.

Squash on merge: yes, will do.

Author display change — replied on the inline thread. Intentional per Chinmay, but happy to revert to .author.login-first if you prefer — your call.

I'll push the rebased + cleaned-up branch shortly, run the canary again on the pinned downstream service end-to-end, then hand off for merge + v4.0.0 tag + @v4 move.

@tarpanpathak tarpanpathak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gamechanger1s , looks good.

Small nit: the alert card isn't quite byte-identical to pre-v4. Date format changed (%Y-%m-%d vs %Y.%m.%d) and the commit-fallback author order flipped. Doesn't matter in practice, just worth softening the wording to "same shape and structure" or listing the deltas.

Approving. Go ahead with the canary; tag v4.0.0 and move @v4 once both cards look right.

@gamechanger1s
gamechanger1s merged commit 0952f1a into main Jul 17, 2026
1 check passed
@gamechanger1s
gamechanger1s deleted the feat/slack-notify branch July 17, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants