toga-ai 1.0.511 → 1.0.512
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/2.0/apps/_underscore/features/error-reporting-issue-event.md +18 -0
- package/knowledge/2.0/apps/worker2/features/error-escalation-cron.md +27 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/INDEX.md +1 -0
- package/knowledge/clients/compass-usa/features/mits-sales-order-transmission-alerting.md +89 -0
- package/knowledge/clients/compass-usa/profile.md +7 -2
- package/knowledge/standalone/apps/claude/INDEX.md +1 -0
- package/knowledge/standalone/apps/claude/workflows/ticket-branch-creation-safety.md +91 -0
- package/package.json +1 -1
|
@@ -364,6 +364,18 @@ in `_underscore` and `library` in the **same** release, and the `/errors` consol
|
|
|
364
364
|
`dbchanges2/Logs_Client/2026-08-01a - Drop Error table.sql`. Do not reintroduce it.
|
|
365
365
|
- **Shared-schema migration collision.** Because these tables live in the *shared* Core Logs
|
|
366
366
|
DB, only **one** migration may `CREATE` them; later migrations only `ALTER`.
|
|
367
|
+
- **The `dbchanges2/Logs/` migration files LAG the deployed schema — read the live cluster, not the
|
|
368
|
+
migrations.** `dbchanges2/Logs/2026-07-30a - Error reporting Issues and Events.sql` does **not**
|
|
369
|
+
describe the live `Logs.Issue`: notably the identity column is **`issueKey` (varchar 128)**, not
|
|
370
|
+
`code`. Live columns include `reference`, `dtLastOccurred`, `isManaged`, `urgency`,
|
|
371
|
+
`minimumUrgency`, `consecutiveQuietWindows`, `subject`, `errorMessage`, `occurrences`,
|
|
372
|
+
`totalOccurrences`, `recurrenceCount`, `dtLastNotified`, `notificationCount`, `dtAcknowledged`,
|
|
373
|
+
`dtMutedUntil`, `createsClickupTask`, `clickupPriority`, `clickupAssigneeId`. Query
|
|
374
|
+
`information_schema` (or `SHOW TABLES` / `SHOW COLUMNS`) against the live Logs cluster **before**
|
|
375
|
+
writing any SQL against this feature — reading the migrations instead produced confidently wrong
|
|
376
|
+
conclusions twice on 2026-08-04. Same trap for the **singular** table names
|
|
377
|
+
(`Issue`/`Event`/`IssueFingerprint`/`IssueAreaOwner`/`IssueClickupTask`/`IssueEmailAddress`,
|
|
378
|
+
renamed by `2026-08-01a` and confirmed applied in prod).
|
|
367
379
|
- **Pre-existing committed secrets in this area (not fixed — out of scope, report only).**
|
|
368
380
|
A ClickUp API token at `_underscore/Component/Api/Clickup/Clickup.php:5`; SES SMTP
|
|
369
381
|
credentials at `_underscore/Email.php:11-12`; a named individual's Sentry personal access
|
|
@@ -418,6 +430,12 @@ clientUserId). **Neither was built.** As built instead:
|
|
|
418
430
|
|
|
419
431
|
## Change history
|
|
420
432
|
|
|
433
|
+
- 2026-08-04 — **Correction/diagnostic:** recorded that the `dbchanges2/Logs/` migration files lag
|
|
434
|
+
the deployed schema — `2026-07-30a` describes a `code` identity column, but live `Logs.Issue` is
|
|
435
|
+
keyed on **`issueKey` (varchar 128)** — and listed the live column set. Query
|
|
436
|
+
`information_schema` against the live Logs cluster before writing SQL here; trusting the
|
|
437
|
+
migrations (and forgetting the applied singular rename) produced two confidently wrong
|
|
438
|
+
conclusions in one session. No code change. (dfranks)
|
|
421
439
|
- 2026-08-04 (later, **uncommitted/undeployed** at time of writing) — **Reference encoding reworked
|
|
422
440
|
to base 32** over `'0123456789ACDEFGHJKMNPQRSTUVWXYZ'` (all digits kept; `O`/`I`/`L`/`B` dropped
|
|
423
441
|
as impersonators of `0`/`1`/`1`/`8`), replacing the base-26 decimal-counter+letter scheme, with
|
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-08-04
|
|
10
|
-
owners: ["jcardinal"]
|
|
10
|
+
owners: ["jcardinal", "dfranks"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Infrastructure/Errors.php
|
|
13
13
|
- worker2/Worker/Notification/Email.php
|
|
@@ -28,6 +28,7 @@ related:
|
|
|
28
28
|
- ./clickup-project-routing.md
|
|
29
29
|
- ./notification-email.md
|
|
30
30
|
- ./notification-email-template.md
|
|
31
|
+
- ../../../clients/compass-usa/features/mits-sales-order-transmission-alerting.md
|
|
31
32
|
---
|
|
32
33
|
|
|
33
34
|
## Summary
|
|
@@ -263,6 +264,22 @@ this controller needs the same treatment.**
|
|
|
263
264
|
issue gets a **neutral** subject rather than another client's raw error text.
|
|
264
265
|
- **`IssueEmailAddress.clientId = 0` means "all clients"** and requires an explicit
|
|
265
266
|
confirmation flag in the Tools console; list views render it as **"ALL CLIENTS"**.
|
|
267
|
+
- **A recipient scoped to a specific `clientId` delivers NOTHING for occurrences recorded without
|
|
268
|
+
client attribution.** `loadRecentClientIds()` maps an occurrence whose `clientId` is NULL to
|
|
269
|
+
**`0`**, and `resolveRecipientsForClient()` resolves scope `0` **plus** the erroring client. So
|
|
270
|
+
the rule is: scope to **`0`** when the `issueKey` is raised from a **single client-specific code
|
|
271
|
+
path** (a Compass-only cron, say); **never** scope to `0` for an `issueKey` raised from shared
|
|
272
|
+
code, which would leak one client's alerts to another's recipients.
|
|
273
|
+
- **Configuring a recipient is never retroactive.** `shouldNotify()` notifies on first occurrence
|
|
274
|
+
(`notificationCount` 0), on escalation, or when the backed-off reminder interval has elapsed —
|
|
275
|
+
and returns **false when the current window has zero events**. The first email therefore lands on
|
|
276
|
+
the **next live occurrence**, not on the backlog. Do not read silence right after a config change
|
|
277
|
+
as a failure.
|
|
278
|
+
- **Adding a business recipient is purely additive — it cannot switch ClickUp ticketing off.**
|
|
279
|
+
Routing is **both**, not either: `if ($recipients !== null)` emails the business recipients and
|
|
280
|
+
`if ($issue->createsClickupTask)` tickets the dev team, evaluated **independently**. The in-code
|
|
281
|
+
comment marking this records the earlier `elseif` form, where adding one recipient silently
|
|
282
|
+
disabled an issue's dev-team ticket.
|
|
266
283
|
- **The ClickUp webhook has no HMAC signature verification** (pre-existing, unfixed). A forged
|
|
267
284
|
POST can mark issues acknowledged — freezing the neglect axis — or resolve episodes. That is
|
|
268
285
|
alert suppression against the error-reporting system itself. Fix before relying on this
|
|
@@ -273,6 +290,15 @@ this controller needs the same treatment.**
|
|
|
273
290
|
|
|
274
291
|
## Change history
|
|
275
292
|
|
|
293
|
+
- 2026-08-04 (verified against `origin/_production` + the prod Logs cluster) — Recorded the
|
|
294
|
+
**recipient-routing semantics** a developer needs before configuring a business alert: routing is
|
|
295
|
+
**both** business email and dev-team ClickUp, evaluated independently (adding a recipient is
|
|
296
|
+
purely additive); `IssueEmailAddress.clientId` scoping means a client-scoped row delivers nothing
|
|
297
|
+
for occurrences with NULL `clientId` (mapped to `0`), so single-client `issueKey`s scope to `0`
|
|
298
|
+
and shared-code `issueKey`s never do; and `shouldNotify()` makes configuration
|
|
299
|
+
**non-retroactive** — the first email lands on the next live occurrence. Also noted that prod
|
|
300
|
+
`Logs.IssueEmailAddress` was still empty, making Compass's MITS rejection the platform's first
|
|
301
|
+
business-routed issue. No code change. (dfranks)
|
|
276
302
|
- 2026-08-04 (later, **uncommitted/undeployed** at time of writing) — **Fixed: escalation email had
|
|
277
303
|
never been sent, ever**, for two independent reasons. (A) `processTechnicalIssue()` returned early
|
|
278
304
|
when the ClickUp task would read identically, and that return sat *above* the `if ($shouldNotify)`
|
package/knowledge/INDEX.md
CHANGED
|
@@ -39,7 +39,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
39
39
|
- **togatech** (TOGA Technology Website) — 4 doc(s) → [standalone/apps/togatech/INDEX.md](standalone/apps/togatech/INDEX.md)
|
|
40
40
|
- **websocket** (WebSocket Server) — 2 doc(s) → [standalone/apps/websocket/INDEX.md](standalone/apps/websocket/INDEX.md)
|
|
41
41
|
- **forward** (Forwarder) — 3 doc(s) → [standalone/apps/forward/INDEX.md](standalone/apps/forward/INDEX.md)
|
|
42
|
-
- **claude** (Claude Harness) —
|
|
42
|
+
- **claude** (Claude Harness) — 4 doc(s) → [standalone/apps/claude/INDEX.md](standalone/apps/claude/INDEX.md)
|
|
43
43
|
|
|
44
44
|
## Clients
|
|
45
45
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
| [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql, dbchanges2/Client/2026-07-15a - ExcludeFeeItemsFromItemFulfillmentsForSalesOrdersView.sql |
|
|
9
9
|
| [Compass MITS PO → SO Item Linking](features/mits-po-to-so-item-linking.md) | 2.0 | MITS sends Compass inbound Purchase Orders (`POST /v2/purchase-orders`) against a Sales Order (`mitsSalesOrder`). | _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
|
|
10
10
|
| [Compass MITS PO Transmission to Vendors](features/mits-po-transmission-to-vendors.md) | 2.0 | The 1.0 worker cron `2_transmit_mits_purchase_orders_to_vendors.php` transmits Compass PurchaseOrders to their vendors (Office Depot, Strategic Systems, Compass | worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, worker/crons/toga2/compasscanada/workflow/2_transmit_mits_purchase_orders_to_vendors.php, library/app/client/compass.php |
|
|
11
|
+
| [Compass MITS Sales-Order Transmission — Rejection Alerting (Issue/Event, not email-in-cron)](features/mits-sales-order-transmission-alerting.md) | 2.0 | When MITS **rejects** a Compass sales order transmitted by the 1.0 cron `1_transmit_compass_sales_orders_to_mits.php`, the alert is no longer an email built ins | worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php, worker2/Worker/Infrastructure/Errors.php, _underscore/Model/Core/Logs/Issue.php, _underscore/Model/Core/Logs/IssueEmailAddress.php, dbchanges2/Logs/2026-08-04a - MITS rejection business recipients.sql |
|
|
11
12
|
| [Compass MR/MA Order Auto-Approval & Status Gate](features/mr-ma-order-approval-and-status.md) | 2.0 | Compass **MR** and **MA** sales orders are system-generated from the MITS / Office Depot EDI pipeline (they do not originate as user-entered SA orders) and must | _underscore/Model/Compass/SalesOrder.php, _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
|
|
12
13
|
| [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
|
|
13
14
|
| [Compass Cross-Kit Bundle Corruption — Detection & Repair](workflows/cross-kit-bundle-corruption.md) | 2.0 | A frontend regression in `toga2-commerce`'s edit-order bundle submission mis-attributed bundle (kit) line items and **fees/warranties** to the **wrong kit**, pe | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts |
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass MITS Sales-Order Transmission — Rejection Alerting (Issue/Event, not email-in-cron)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: _Underscore
|
|
5
|
+
client: compass-usa
|
|
6
|
+
type: client-feature
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-08-04
|
|
9
|
+
owners: ["dfranks"]
|
|
10
|
+
files:
|
|
11
|
+
- worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php
|
|
12
|
+
- worker2/Worker/Infrastructure/Errors.php
|
|
13
|
+
- _underscore/Model/Core/Logs/Issue.php
|
|
14
|
+
- _underscore/Model/Core/Logs/IssueEmailAddress.php
|
|
15
|
+
- dbchanges2/Logs/2026-08-04a - MITS rejection business recipients.sql
|
|
16
|
+
related:
|
|
17
|
+
- mits-po-transmission-to-vendors.md
|
|
18
|
+
- ../../../2.0/apps/worker2/features/error-escalation-cron.md
|
|
19
|
+
- ../../../2.0/apps/_underscore/features/error-reporting-issue-event.md
|
|
20
|
+
- ../../../1.0/apps/library/features/error-capture-1-0.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Summary
|
|
24
|
+
|
|
25
|
+
When MITS **rejects** a Compass sales order transmitted by the 1.0 cron
|
|
26
|
+
`1_transmit_compass_sales_orders_to_mits.php`, the alert is no longer an email built inside the
|
|
27
|
+
cron. It is a **business exception** raised into the shared Logs Issue/Event pipeline, and its
|
|
28
|
+
recipients are **rows in `Logs.IssueEmailAddress`**.
|
|
29
|
+
|
|
30
|
+
**Consequence for anyone handed a ticket like "add <person> to the MITS error emails": there is
|
|
31
|
+
no PHP to change.** It is a one-row data change delivered as a `dbchanges2/Logs/` migration.
|
|
32
|
+
|
|
33
|
+
## How it works
|
|
34
|
+
|
|
35
|
+
- The cron raises
|
|
36
|
+
`throw new App_Exception_Business('COMPASS_SALES_ORDER_MITS_TRANSMIT_REJECTED', …, URGENCY_HIGH)`
|
|
37
|
+
on a MITS rejection. (Before 2026-08-03 this block was a hardcoded `new App_Email_Agilant()`
|
|
38
|
+
with `addTo('devteam@togatech.com')`.)
|
|
39
|
+
- Capture creates/dedupes a `Logs.Issue` keyed on the `issueKey`, and the worker2
|
|
40
|
+
`Infrastructure/Errors/Escalate` cron does all outbound notification — business email to the
|
|
41
|
+
configured recipients **and**, independently, the dev-team ClickUp task. See
|
|
42
|
+
[error-escalation-cron](../../../2.0/apps/worker2/features/error-escalation-cron.md).
|
|
43
|
+
- Recipients live in `Logs.IssueEmailAddress` (FK `issueId` → `Logs.Issue`), scoped by
|
|
44
|
+
`clientId` (`0` = all clients).
|
|
45
|
+
|
|
46
|
+
### Adding a recipient (the correct pattern)
|
|
47
|
+
|
|
48
|
+
Write a `dbchanges2/Logs/YYYY-MM-DD<letter>` migration that:
|
|
49
|
+
|
|
50
|
+
1. **Looks the issue up by `issueKey`**, never by its `id`. The id is assigned on the issue's
|
|
51
|
+
**first occurrence** and is therefore not stable across environments.
|
|
52
|
+
2. Uses **`clientId = 0`**. This `issueKey` is raised from a Compass-only code path, and an
|
|
53
|
+
occurrence recorded without client attribution resolves against scope `0` only — a row scoped
|
|
54
|
+
to the Compass `clientId` can deliver nothing. (Never use `0` for an `issueKey` raised from
|
|
55
|
+
shared code — that is a cross-tenant leak.)
|
|
56
|
+
3. Is **idempotent** via `INSERT … WHERE NOT EXISTS`, mirroring the unique key
|
|
57
|
+
`uq_issue_client_address_type`.
|
|
58
|
+
|
|
59
|
+
Configuration is **never retroactive**: `shouldNotify()` fires on the first occurrence, on
|
|
60
|
+
escalation, or when the backed-off reminder interval elapses, and returns false for a window with
|
|
61
|
+
zero events. The first email lands on the **next live rejection**, not on existing history.
|
|
62
|
+
|
|
63
|
+
## Gotchas / known issues
|
|
64
|
+
|
|
65
|
+
- **Do not "modernize" this alert back into the cron.** Migrating it to
|
|
66
|
+
`_Model_Client_EmailTemplate::send` (or any in-cron email) reverts the move to the Issue/Event
|
|
67
|
+
pipeline and removes the recipient configuration surface entirely.
|
|
68
|
+
- **Never re-introduce the payload dump into the alert.** The original email included the
|
|
69
|
+
transmission payload, which carries ship-to addressee, attention line, street address and phone
|
|
70
|
+
number. The Issue message is stored in a table **shared across every client**, displayed to
|
|
71
|
+
internal staff in the Tools `/errors` console, pushed to ClickUp, and emailed — so the payload
|
|
72
|
+
was deliberately removed. Alerts here name the order, not the person.
|
|
73
|
+
- **The MITS cron holds its API credentials inline in the file.** Treat that file as
|
|
74
|
+
secret-bearing when copying snippets anywhere (tickets, PR bodies, knowledge docs).
|
|
75
|
+
- **Adding a recipient does not turn ClickUp ticketing off.** Routing is both-and, not
|
|
76
|
+
either-or — see the escalation-cron doc for the `elseif` bug this used to be.
|
|
77
|
+
- As of 2026-08-04 prod `Logs.IssueEmailAddress` is empty, so this is the **first business-routed
|
|
78
|
+
issue on the platform**: `processBusinessIssue()` will execute in production for the first time
|
|
79
|
+
when this issue next fires. Watch that run rather than assuming it works.
|
|
80
|
+
|
|
81
|
+
## Change history
|
|
82
|
+
- 2026-08-04 — Documented that MITS rejection alerting moved (worker `5521854b`, 2026-08-03) from a
|
|
83
|
+
hardcoded in-cron `App_Email_Agilant` to `App_Exception_Business` +
|
|
84
|
+
`Logs.IssueEmailAddress` recipients, so adding an alert recipient is a data change, not a code
|
|
85
|
+
change. Recorded the deliberate removal of the PII-bearing payload dump from the alert, the
|
|
86
|
+
look-up-by-`issueKey` / `clientId = 0` / `NOT EXISTS` migration pattern, and that recipient
|
|
87
|
+
configuration only takes effect on the next live occurrence. (dfranks)
|
|
88
|
+
</content>
|
|
89
|
+
</invoke>
|
|
@@ -16,10 +16,11 @@ project: _Underscore
|
|
|
16
16
|
client: compass-usa
|
|
17
17
|
type: profile
|
|
18
18
|
status: active
|
|
19
|
-
updated: 2026-
|
|
20
|
-
owners: [jcardinal, bala, tcox, apeterson]
|
|
19
|
+
updated: 2026-08-04
|
|
20
|
+
owners: [jcardinal, bala, tcox, apeterson, dfranks]
|
|
21
21
|
files: []
|
|
22
22
|
related:
|
|
23
|
+
- features/mits-sales-order-transmission-alerting.md
|
|
23
24
|
- features/asn-to-item-fulfillment.md
|
|
24
25
|
- features/cost-centers.md
|
|
25
26
|
- features/approval-decision-flow.md
|
|
@@ -75,6 +76,10 @@ separate, related client (see its own profile).
|
|
|
75
76
|
`worker/crons/toga2/compass/workflow/3b_import_strategic_systems_advance_shipping_notices.php`.
|
|
76
77
|
|
|
77
78
|
## Key features (this client)
|
|
79
|
+
- [MITS Sales-Order Transmission — Rejection Alerting](features/mits-sales-order-transmission-alerting.md)
|
|
80
|
+
— MITS rejection alerts are `App_Exception_Business` Issues with recipients in
|
|
81
|
+
`Logs.IssueEmailAddress`, **not** an email in the cron: adding an alert recipient is a
|
|
82
|
+
`dbchanges2/Logs/` data change with no PHP.
|
|
78
83
|
- [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) — the
|
|
79
84
|
`_Model_Compass_AdvanceShippingNotice::postPost` handler.
|
|
80
85
|
- [Cost Centers (Unit Locations, numeric-only)](features/cost-centers.md) — what a Compass
|
|
@@ -5,3 +5,4 @@
|
|
|
5
5
|
| [Harness Distribution — How a knowledge.js Fix Reaches Teammates](workflows/harness-distribution.md) | There are **two copies** of `knowledge.js` on a developer's machine, and the skills use only one of them. | claude/scripts/install.js, claude/knowledge.js |
|
|
6
6
|
| [Knowledge Base Publish / Push Pipeline](workflows/knowledge-publish-pipeline.md) | How `/capture` and `/session-save` get knowledge docs from your working tree into `agilantsolutions/claude` on `_main`. | claude/knowledge.js |
|
|
7
7
|
| [MCP Tool Usage — "Talos" Means the Internal Knowledge Base, and Run Your Own SELECTs](workflows/mcp-tool-usage.md) | Two MCP connections are available in every session, and **Claude is expected to reach for them on its own judgement — nobody should have to say the word "MCP".* | claude/.claude/skills/kickoff/SKILL.md, claude/.claude/skills/capture/SKILL.md |
|
|
8
|
+
| [Ticket-Branch Creation Safety — How /work-ticket Armed Pushes to Production Branches](workflows/ticket-branch-creation-safety.md) | For roughly a month, `/work-ticket` created ticket branches whose **git upstream was the production branch**. | claude/skills/work-ticket/SKILL.md |
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ticket-Branch Creation Safety — How /work-ticket Armed Pushes to Production Branches
|
|
3
|
+
framework: "standalone"
|
|
4
|
+
repo: claude
|
|
5
|
+
project: Claude Harness
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-04
|
|
10
|
+
owners: ["dfranks"]
|
|
11
|
+
files:
|
|
12
|
+
- claude/skills/work-ticket/SKILL.md
|
|
13
|
+
related:
|
|
14
|
+
- ./harness-distribution.md
|
|
15
|
+
- ../../../2.0/apps/worker2/workflows/ticket-to-pseudocode-planning.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
For roughly a month, `/work-ticket` created ticket branches whose **git upstream was the
|
|
21
|
+
production branch**. Claude's own pushes were unaffected, but a developer using VS Code's
|
|
22
|
+
**Commit + Sync** mid-session pushed ticket code straight to `_main`/`_production` — no PR, no
|
|
23
|
+
review. Two such pushes landed in two days before it was found.
|
|
24
|
+
|
|
25
|
+
The mechanism is generic git behavior, so it is worth understanding beyond this one skill.
|
|
26
|
+
|
|
27
|
+
## Root cause
|
|
28
|
+
|
|
29
|
+
Branch creation was `git checkout -b TRUE-XXXX "origin/$DEFAULT"`. Git's
|
|
30
|
+
**`branch.autoSetupMerge` defaults to `true`**, so branching from a *remote-tracking ref* adopts
|
|
31
|
+
that ref as the new branch's **upstream**. The ticket branch therefore tracked
|
|
32
|
+
`origin/_main` / `origin/_production` until the skill's later `git push -u` re-pointed it.
|
|
33
|
+
|
|
34
|
+
**Trigger asymmetry is why it hid.** Claude pushes with `git push -u origin <TICKET>` — an
|
|
35
|
+
explicit refspec, which ignores the upstream and was always safe. VS Code's Commit + Sync resolves
|
|
36
|
+
the destination **from the upstream**, so any developer commit made before the skill's first push
|
|
37
|
+
went to the production branch.
|
|
38
|
+
|
|
39
|
+
**A safety fix created the exposure.** Commit `6d155f7` (2026-07-09) moved branch creation from
|
|
40
|
+
*after* implementation to *before* any code is written — itself a genuine fix, because a stale
|
|
41
|
+
local `_production` 12 commits behind nearly reverted upstream work. But it turned a seconds-long
|
|
42
|
+
armed window into the **entire implementation phase**. Correctness fixes can lengthen an existing
|
|
43
|
+
window; check what else depends on the timing you moved.
|
|
44
|
+
|
|
45
|
+
## The fixes
|
|
46
|
+
|
|
47
|
+
1. **Skill:** branch creation uses **`--no-track`** *and* publishes the empty branch immediately
|
|
48
|
+
(`git push -u origin TRUE-XXXX`), so the branch has a correct upstream before any code exists.
|
|
49
|
+
The later push is a plain `git push origin <TICKET>`.
|
|
50
|
+
2. **Global git config:** `branch.autoSetupMerge=simple` (a checkout can no longer adopt a
|
|
51
|
+
production upstream, even outside the skill) plus `push.autoSetupRemote=true`.
|
|
52
|
+
3. **`pre-push` hook** in each app repo rejecting
|
|
53
|
+
`refs/heads/(_main|_production|_beta|_alpha|master)`, override `ALLOW_PROD_PUSH=1`. This also
|
|
54
|
+
catches Claude, because Claude shells out to real `git`.
|
|
55
|
+
4. **Deny rules** in `.claude/settings.json`.
|
|
56
|
+
5. Existing branches still tracking a production branch were **disarmed** individually.
|
|
57
|
+
|
|
58
|
+
`rework-ticket` was never affected — it checks out an existing remote branch.
|
|
59
|
+
|
|
60
|
+
## Recovery: getting an unreviewed commit off an unprotected default branch
|
|
61
|
+
|
|
62
|
+
Do **not** force-push. Instead:
|
|
63
|
+
|
|
64
|
+
1. **Revert via PR** on the default branch.
|
|
65
|
+
2. **Rebuild the ticket branch as a NEW commit** on the post-revert base.
|
|
66
|
+
|
|
67
|
+
> **The revert-remerge trap:** if you keep the original commit and merge that branch later, the
|
|
68
|
+
> original commit is already an **ancestor** of the default branch, so the merge is a silent no-op
|
|
69
|
+
> and the change **never comes back**. The revert wins and nothing warns you.
|
|
70
|
+
|
|
71
|
+
## Gotchas / known issues
|
|
72
|
+
|
|
73
|
+
- **Everything installed here is local and does not survive a fresh clone.** Hooks and git config
|
|
74
|
+
are per-machine; a teammate or a new laptop is unprotected until they redo it.
|
|
75
|
+
- **Standing exposure (not fixed):** `dbchanges2/_main`, `worker/_production`,
|
|
76
|
+
`worker2/_production` and `api2/_production` have **no branch protection and no rulesets**.
|
|
77
|
+
Server-side protection is the only durable fix and needs an admin GitHub account.
|
|
78
|
+
- **Diagnosing this class of incident:** `git reflog` on the remote-tracking branch shows
|
|
79
|
+
`update by push` entries — that is how both incidents were confirmed and how the other four
|
|
80
|
+
repos were cleared. Pre-existing `_beta` pushes are normal manual practice; check dates before
|
|
81
|
+
calling something an incident.
|
|
82
|
+
- Auditing "did this ever happen elsewhere?" requires sweeping **every** repo the skill touches,
|
|
83
|
+
not just the one that surfaced the bug.
|
|
84
|
+
|
|
85
|
+
## Change history
|
|
86
|
+
- 2026-08-04 — Documented the `branch.autoSetupMerge` root cause, the Claude-vs-VS-Code trigger
|
|
87
|
+
asymmetry that hid it for a month, and the fixes (`--no-track` + immediate branch publish,
|
|
88
|
+
global `autoSetupMerge=simple`, per-repo `pre-push` hook, settings deny rules). Recorded the
|
|
89
|
+
revert-then-rebuild recovery pattern and its revert-remerge no-op trap, plus the standing lack
|
|
90
|
+
of server-side branch protection on four app repos. (dfranks)
|
|
91
|
+
</content>
|
package/package.json
CHANGED