All articles
October 10, 20255 min read

Building a Sentry Release GitHub Action: Lessons Learned

What I learned creating a custom GitHub Action for Sentry releases, from inputs and errors to composition and years in production.

DevOpsGitHub ActionsCI/CDSentry

When I built the Sentry Release GitHub Action, the existing options did not fit our workflow. We needed something that worked with self-hosted Sentry instances and integrated cleanly with semantic-release. So I built one. It has been running in production across multiple organizations for years. Here is what I would do differently, and what I would keep the same.

Why Custom Actions

Off-the-shelf Sentry actions assumed a certain setup: cloud Sentry, specific auth patterns, or pre-baked steps that did not match our pipeline. We deploy to multiple environments. We use semantic-release for versioning. We needed an action that could receive a version, an environment, and optional deploy metadata, then create a Sentry release with the right commits and deployment info.

Building custom gave us control. The trade-off: we own maintenance and bug fixes. For our use case, that was acceptable.

Anatomy of a GitHub Action

An action is a action.yml manifest plus executable code. The manifest defines inputs, outputs, and runtime.

yaml
1name: 'Sentry Release'
2description: 'Create a Sentry release with commits and deploys'
3inputs:
4 version:
5 description: 'Release version'
6 required: true
7 environment:
8 description: 'Deployment environment'
9 required: false
10 default: 'production'
11runs:
12 using: 'node12'
13 main: 'index.js'
14

Simple. The tricky part: inputs are always strings. If you need a boolean, you must parse "true" and "false". If you need a list, you need a convention (e.g. comma-separated) and parse it yourself. Document this clearly. Users will pass true (unquoted) or false and wonder why it does not work.

Environment Variables and Secrets

Sentry needs an auth token, org slug, and project name. We could have made these inputs. We chose environment variables instead. Reasons: inputs clutter the workflow file; secrets as inputs can leak in logs if misused; env vars are the standard for credentials.

So the workflow looks like:

yaml
1- uses: our-org/sentry-release-action@v1
2 env:
3 SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
4 SENTRY_ORG: ${{ vars.SENTRY_ORG }}
5 SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
6 with:
7 version: ${{ github.ref_name }}
8 environment: production
9

Clean. The action reads from process.env. We validate that required vars are set before calling the Sentry API. If something is missing, we fail fast with a clear message.

Error Handling: Do Not Fail Silently

GitHub Actions fail silently by default. If your script throws, the step fails. But if you catch an error and log it without re-throwing, the action can succeed while the job is effectively broken. Always surface failures clearly when something goes wrong.

For network calls (and Sentry API calls are network calls), add retries. Transient failures happen. We retry up to 3 times with exponential backoff for 5xx responses. For 4xx, we fail immediately; retrying won't help.

Another gotcha: the Sentry API might return 200 but with an error in the body. We check the response payload. If the release was not created, we fail.

Composing Actions

Our release pipeline chains semantic-release with the Sentry action. semantic-release bumps the version, tags, and publishes. Then we run the Sentry action with the new version. Every release automatically gets a Sentry release so we can trace errors to a specific deploy.

The key: outputs. Our action exposes outputs that downstream steps can use. For example, a step that posts to Slack can include the release link.

Input Validation

Users will pass invalid data. Wrong version format. Empty strings. We validate everything up front. If the version does not match a regex we expect, we fail with a message like "version must be a valid semver string." Better to fail fast than to have the Sentry API return a cryptic error.

We also validate that we are in a context where creation makes sense. For example, if the workflow runs on a pull request, we might skip or warn. Document the intended usage.

Minimal Dependencies

We keep dependencies minimal. The action uses the Sentry API directly via HTTPS. No heavy SDK unless we need it. Fewer dependencies mean fewer security advisories, faster setup, and fewer version conflicts. Every dependency is a liability. Add only what you need.

Documentation and Support

If your action is public, documentation matters. README with a quick start, input/output reference, and a minimal workflow example. Add a "Troubleshooting" section when you see repeated questions. We added one after users asked about self-hosted Sentry and token scopes. It saved a lot of back-and-forth.

What I Would Do Differently

I would add a dry-run mode earlier. Being able to run the action without creating a release helps debugging. We added it later. Would have saved time.

I would also add more granular logging. In CI, you often debug by reading logs. Structured logging (e.g. JSON) or at least consistent prefixes make it easier to grep. We improved this over time.

Conclusion

Building a custom GitHub Action for Sentry taught me the importance of minimal dependencies, clear documentation, and strict input validation. Error handling cannot be an afterthought. The action has been running in production for years. If you need the same control, building custom is viable. Just plan for the long run.