Skip to content

v0.18.0

Choose a tag to compare

@github-actions github-actions released this 19 Oct 09:07

Added

  • CHANGELOG conflicts resolve themselves when that is safe. When main's CHANGELOG.md changes (and after each Release), a new workflow merges main into every open PR from this repo that edits it. If CHANGELOG.md is the only conflict, it keeps both sides' [Unreleased] entries, pushes the merge and comments on the PR. It never adds a section twice, puts entries into the new [Unreleased] after a release, and comments with the reason instead when both sides changed the same entry or the file isn't plain Keep a Changelog. Locally, just changelog-merge does the same in a stopped merge or rebase, and just changelog-merge-driver makes git do it by itself. For CI to run on those merges, a maintainer sets up a GitHub App (runbook: "Changelog conflicts").

Changed

  • CI jobs run only when the files they depend on have not already passed. Each workflow's first job hashes the job's inputs (a list in .github/path-filters.yml, plus the workflow running it) and looks for a marker that an earlier run with exactly those files succeeded. So pushing only a CHANGELOG entry, re-pushing the same code, or opening a docs-only PR no longer runs just check, the Docker build, the docs site build or Terraform validate again. Skipped jobs no longer start a runner. Test-only changes no longer build or publish an image. CD publishes when the image inputs differ from the last publish, so a CD run cancelled by a newer merge no longer leaves code without a GHCR image. Terraform plan jobs that aren't needed never start, so they never wait on an Environment. The changelog gate no longer runs pnpm install. just ci-inputs <list> shows what a list matches; the runbook's "CI and CD" says how to force a rerun.
  • just secrets-push / just push are now the only writers of the app secrets in Key Vault (Discord token, admins.yaml, Home Assistant, Mastodon, space state, Sentry). Terraform's write_secrets writes only tailscale-auth-key and ghcr-pull-token, and the app-secret Terraform inputs (discord_token, admins_yaml_file, sentry_dsn, spacestate_user and the rest) are gone, so turning on a feature no longer makes an apply demand or collide with secrets the sync tool set. Push an app secret before the apply that turns its feature on. The next just tf-plan dev|prod drops the app secrets Terraform had written from state ("will no longer be managed by Terraform, but will not be destroyed"); nothing leaves Key Vault. Delete the app-secret lines from any secrets.auto.tfvars. See ADR 0017.

Fixed

  • /spacestate: the Sentry context for a failed call now says why the network failed (errorCause, such as ENOTFOUND, ECONNREFUSED or UNEXPECTED_REDIRECT), and shows the address without its query. Sentry was replacing the old redacted address with [Filtered].
  • Home Assistant: a call cut off by a dropped connection now says it can't reach Home Assistant, instead of being logged as an unexpected failure and sent to Sentry as "Object captured as exception".
  • Home Assistant: if Pixel can't reach Home Assistant at startup, it now says so within about 15 seconds (home.connect_failed in the logs, once to Sentry) and keeps trying in the background, waiting longer each time up to a minute. Before, it retried every second forever without a word, and the login and devices checks never ran. When it does connect, it logs home.ready and runs those checks.
  • /admin level set and capability changes no longer write members.yaml when the file was hand-edited into a state that fails the startup cross-checks (an id in two entries, or an admin with no entry). Before, the file was saved but the in-memory view and the access.changed audit record were not, and the admin got a generic error. Now the change is refused with "The members file is invalid", and nothing is written.
  • /schedule: a schedule deleted, paused or edited while another one due at the same time was posting no longer goes out as it was. Pixel reads each schedule again right before posting it.
  • /schedule: a message or poll that {{tokens}} would push past Discord's length limits (2000 characters for a message, 300 for a question, 55 for an answer) is now refused when it's created or edited, instead of failing at every occurrence. One that still comes out too long when it's posted is cut to fit and logged as schedule.clipped.
  • /schedule edit: changing when, repeat or days now counts from the edit. Before, a weekly post that last went out on Monday and was moved to Thursday at 19:20 could post straight away for that Thursday 19:00, while the reply said next week.
  • /schedule edit refuses to turn a repeat into a one-off whose start has passed (add a new when), and /schedule resume refuses a one-off whose time passed while it was paused (it says to delete it). Both used to keep a schedule that could never post.
  • /schedule when option: its description names the configured PIXEL_TIMEZONE instead of always saying Europe/Amsterdam. Re-register the commands (just register) for a zone other than Europe/Amsterdam to see it.
  • Discord: when Discord refused a slow command's acknowledgement (its deferral), the failure is no longer also logged and sent to Sentry as an unhandled rejection with no user or command; it is handled once, by the adapter. A command whose "Checking…" placeholder couldn't be posted (/status, /ha and others) is no longer reported as a failed command: the handler still doesn't run, and the adapter logs it like any other refused acknowledgement.
  • Discord: only "already acknowledged" (40060) is logged as discord.duplicate_handler (a second Pixel on the token). "Unknown interaction" (10062) means Pixel didn't acknowledge within Discord's 3 seconds, so it is now logged as discord.slow_ack with the command and who ran it, and reported to Sentry as a SlowAcknowledgementError naming the command.
  • Sentry: traces no longer carry Discord interaction tokens. Every reply to a command goes to a Discord URL with the interaction token in its path, and the request's span (url.full, url.path) and http breadcrumb kept it, so anyone with access to the Sentry project could post as the bot for 15 minutes after each command. Spans now go through the same scrubbing as errors, breadcrumbs and logs, and the token in /interactions/<id>/<token> and /webhooks/<id>/<token> paths is shown as [redacted]. The scrubbing also no longer skips an object that appears twice in what is sent, and keeps an error's message and stack (scrubbed) instead of dropping them.
  • The Release workflow runs just check on the main commit it is about to release, and only tags, publishes and deploys to prod if it passes. CI on main can be cancelled or red and never runs on the bump commit, so before this nothing tested what reached prod.
  • Sentry errors from the Docker image now show the TypeScript source. The image ran Node with --enable-source-maps, which rewrote stack frames to src/*.ts before Sentry saw them, so the source maps uploaded for /app/dist never matched and issues had no source context. Node now runs without it and Sentry applies the uploaded maps. Stack traces in the container logs and log files now show the compiled dist/*.js lines.
  • /admin doors on|off: the home.kind_switched log now names the admin like every other action (Discord ID, name and handle) and carries the command, so moderators can recognise who switched door control. Before, it had only the Discord ID. It is still logged once per switch.
  • Bot status: a run that had no online post of its own (stopped within the first few seconds, or its online post failed) no longer edits the newest online post to "offline" when it shuts down. During a deploy that post is the new version's, so the channel said Pixel was offline while it was running. It now says offline in a new post and leaves other runs' posts alone, and a run that is shutting down no longer posts "online" afterwards.
  • Mastodon: a response from the instance is now read only up to the 64 KiB limit. A larger body (declared or streamed without a length) is cancelled instead of buffered in full or left open.
  • Commands with a form (/schedule, closing time): opening the form now takes a token from the per-person command rate limit (refused and logged as command.rate_limited like any command), and errors reported while the form is being prepared are tied to the person's Discord ID in Sentry, as they are while a command runs. Submitting the form still takes its own token.
  • /schedule: when no longer guesses. Four digits after a date are read the same with or without a month name: a time (14 nov 1900 is 19:00, 14 nov 2030 is 20:30, as 14-11 2030 already was), and a year only beside another time (14 nov 2030 19:00) or when no clock reads them (14 nov 2099). Months must be English names or their short forms (sep, sept, september), so words like junk, decent or mars are refused instead of read as June, December or March.
  • CD on main no longer loses an image: runs queue instead of cancelling each other, and each one publishes when the image inputs differ from the last run that published, not just from the previous push. Before, a docs-only merge right after a code merge cancelled that merge's publish and then skipped its own, so the code merge never got a GHCR image or a Sentry release.
  • The Discord DM sent when a capability is granted or revoked now really has mentions turned off. It was sent with allowedMentions, a field the Discord API ignores, instead of allowed_mentions.
  • The Sentry heartbeat no longer runs when PIXEL_ENV=local. A laptop running just dev with a DSN used to check in to a shared pixel-local cron monitor, which opened an issue about ten minutes after anyone stopped it. Startup now logs heartbeat.off with the reason. pixel-dev and pixel-prod are unchanged.

Changed

  • Laptop tooling now shares one definition of each environment (#116). scripts/envs.ts holds every Azure name (subscription, resource group, Container App, Key Vault, file share), the env file and the guards, and just pull/push, secrets-pull/push, ha-devices, register, command-access, restart, deploy and tf-apply all take them from there. New just env dev|prod shows where an environment lives. just --list is grouped (develop, environments, config, terraform, release, docs). just deploy dev replaces just deploy-dev, which still works for now; just deploy prod refuses because prod ships from the Release workflow. Discord tools load their env file in TypeScript rather than through a bash helper, still dropping anything just loaded from .env.