Playbook: Pinbox24 W4 — HR wniosek (holidays-apply-process) submission failures — activate_trigger bug class

Incident date: 2026-08-06 Resolved: 2026-08-06 — three independent, pre-existing bugs found + fixed in pinbox24/p24-back-ts (GitLab) Severity: P2 — user-visible “cannot submit HR wniosek in W4” (holiday/leave application), silent since ~2024 (43 error instances on bug 1) Repo (code): pinbox24/p24-back-ts on GitLab — the W4 backend (v42-prod). Not p24-infra. Service affected: v42-prod process engine, holidays-apply-processactivate_trigger.ts flow GH tracking: #5764 (investigation) · follow-ups #5755 (Mailgun key reconciliation), #4948 (INTEGRATION_AUTH_TOKEN — expedited same-day, closed) GitLab MRs (code-level fixes):

  • p24-back-ts !801 + repair !802 — bug 1 (processDoc not passed to task context)
  • p24-back-ts !804 — bug 2 (numeric 0 misclassified as empty)
  • p24-back-ts !806 — bug 3 (dead hardcoded Mailgun API key)

Why this playbook lives in p24-infra, not GitLab. The fixes are in the GitLab W4 backend (sys-admin / infra-task-owned W3/W4 stack — see ../w3-w4-stack-operations.md). This repo captures the investigation + bug class so it is recoverable and auditable from the infra side. A p24-infra dev-coder cannot open MRs to that GitLab repo; the code fixes were made through the sanctioned W4 path.


Symptoms

  • User reports: “cannot submit HR wniosek (holiday/leave application) in W4”.
  • The holidays-apply-process never advanced past its activate_trigger step for the affected submission.
  • On investigation, activate_trigger.ts’s getNextAutoTrans() never found an auto-transition even when a real one was configured — isAutoTransPresent was always false.
  • ~43 historical error instances for bug 1, silent since ~2024 (no user-facing crash, the process just stalled), so it had never been isolated before.

Root causes

All three sit in the holidays-apply-process activate_trigger flow. They are independent — each was uncovered only after the previous one was fixed and the flow advanced one step further.

Bug 1 — processDoc not passed to the task context (!801 / repair !802)

processButtonClickedHelper resolved the full process document but passed the bare processId ObjectId as the task context’s process field. activate_trigger.ts’s getNextAutoTrans() then read data.process.transitions — always undefined on a bare ObjectId — so isAutoTransPresent was always false regardless of whether a real auto-transition existed.

Fix: pass the already-fetched processDoc object (which carries .transitions) into the task context instead of the raw ID.

Tooling footgun (recorded so it does not recur): MR !801’s first push corrupted the target file to a single line 1 because of a PowerShell/JSON newline-encoding bug in the push tooling — this broke back-end-build on master for ~15 min until !802 repaired it. No live impact (the deploy never ran on the broken commit). This is the same newline-encoding hazard called out for SOPS writes in CLAUDE.md — never write repo files with > / Out-File / Set-Content from PowerShell; use [System.IO.File]::WriteAllText(..., UTF8 no-BOM).

Bug 2 — numeric 0 misclassified as “empty” (falsy-zero) (!804)

Same file. if (!valueToCheck) treated a legitimate empActualYearCheck count of 0 as empty (JavaScript falsy), skipping the process’s explicit {key:"0", activitie:48} routing rule and falling through to the (correctly absent, in this case) auto-transition fallback. A commented-out prior fix attempt for this exact issue was found on the line directly above — someone had hit it before and backed out.

Fix: introduce an explicit emptiness test and use it consistently:

const valueIsEmpty = value === null || value === undefined || value === "";

so a real 0 is routed by the {key:"0", …} rule instead of being swallowed as “empty”.

Bug class — audit target. !value / if (!x) as an “is it empty?” test is wrong for any field whose legitimate value can be 0, "", or false. The issue notes this pattern is not specific to holidays-apply-process — other activate_trigger usages across Pinbox24 should be audited for the same falsy-zero mistake. See Follow-ups below.

Bug 3 — dead hardcoded Mailgun API key (!806)

src/constants/common.const.ts’s MAILGUN_SETTINGS.API_KEY was a hardcoded literal, confirmed dead (401 against the Mailgun EU API). The live v42-prod container already had a valid, unused MAILGUN_API_KEY env var (sourced from SOPS secrets/pinbox24-w4.env.sops key V42_MAILGUN_API_KEY, wired via an earlier out-of-band hotfix — history in #4169 / #4306 / #4416).

Fix: de-hardcode to process.env.MAILGUN_API_KEY. Verified via a real test send: env-var key → 200 delivered; hardcoded literal → 401.

Secret hygiene. Reference the key NAME only (V42_MAILGUN_API_KEY / MAILGUN_API_KEY) — never quote the value. Secrets live in SOPS+age (secrets/pinbox24-w4.env.sops). Any rotation or reconciliation is a secret-manager action, not dev-coder.


Verification method (read-only, no value ever printed)

End-to-end verification after each fix used read-only MongoDB queries against w4_db.process.instances with the w4_app role (not admin) plus PM2 log inspection on bms-1 (94.23.26.113) — see the app-user vs admin split in ../w3-w4-stack-operations.md.

Final confirmed instance 6a74bd2d3fe1ec003537b2cd (2026-08-06T16:58:37Z) reached:

  • state: 50“Pracownik nie zarejestrowany w rejestrze hr na aktualny rok”
  • stateTaskStage: pending

This is the correct terminal state for an employee not yet in the HR registry for the current year — not an error. User confirmed submission then succeeded.


Also surfaced this session (context, tracked separately)

  • #4948 — INTEGRATION_AUTH_TOKEN gap (unrelated live incident). Deliberately deferred to the 2026-08-07 window, then expedited same-day after confirming live user impact (v42-prod / s3-v2-v42-prod had zero relevant env vars since 2026-08-04 23:59). Wired via secrets/pinbox24-w4.env.sops key V42_INTEGRATION_AUTH_TOKEN; secrets-sync.yml recreated both containers; verified live. Closed by the secret-manager follow-up.
  • #5755 — Mailgun key three-way divergence (new, open). SOPS V42_MAILGUN_API_KEY, the former master-hardcoded literal, and the live container’s MAILGUN_API_KEY are three different values, only two of which (SOPS, live container) are currently valid. A future secrets-sync + container recreate would silently swap the live working key for the SOPS one with no documented reconciliation. Needs a secret-manager/human decision on which value is canonical before that is safe.

Follow-ups

  1. #5755secret-manager/human decision on the canonical Mailgun key value (see above) before the next secrets-sync recreate touches v42-prod / s3-v2-v42-prod.
  2. Audit other activate_trigger usages across Pinbox24 for the bug-2 falsy-zero pattern (!value used as an emptiness test where 0 is a legitimate value). This bug class is not specific to holidays-apply-process. Open a dev-issue against pinbox24/p24-back-ts (GitLab) via the W3/W4 stack path if/when scoped.