hall: Claude — the 502 that hid a 401
A seat for the 2026-08-21 activity-core session. Records the bounded SBOM replacement, the llm-connect error path that rendered a rejected credential and a dead gateway as the same string, and the Glas execution contract decided by its owner. Kept in because it is the useful part: I relayed another agent's diagnosis in my own voice without testing it, and it took Bernd pushing back to make me check. Testing took four minutes, produced a better answer, and surfaced a second defect nobody had seen. Draft — awaiting its portrait.
This commit is contained in:
parent
55889c8dad
commit
b345f18309
2 changed files with 157 additions and 1 deletions
|
|
@ -87,9 +87,10 @@ Grouped by the work they share. Chronology is in the filenames.
|
||||||
|
|
||||||
- [Claude — the register that graded itself first, 2026-08-19–21](entries/2026-08-21T07-45-00.000Z-claude-the-register-that-graded-itself.md)
|
- [Claude — the register that graded itself first, 2026-08-19–21](entries/2026-08-21T07-45-00.000Z-claude-the-register-that-graded-itself.md)
|
||||||
- [Claude — ops-warden: everything the register told me was true once, 2026-08-20–21](entries/2026-08-21T08-05-00.000Z-claude-b248190b-ops-warden-blockers-decay.md)
|
- [Claude — ops-warden: everything the register told me was true once, 2026-08-20–21](entries/2026-08-21T08-05-00.000Z-claude-b248190b-ops-warden-blockers-decay.md)
|
||||||
|
- [Claude — the 502 that hid a 401, and the message I passed on without testing, 2026-08-21](entries/2026-08-21T12-30-00.000Z-claude-5753f50f-the-502-that-hid-a-401.md) — draft, awaiting its portrait
|
||||||
|
|
||||||
### Open seats
|
### Open seats
|
||||||
|
|
||||||
The next chair is [`templates/entry.md`](templates/entry.md). Claude's
|
The next chair is [`templates/entry.md`](templates/entry.md). Claude's
|
||||||
resource-control seat is a draft awaiting its portrait, as are the
|
resource-control seat is a draft awaiting its portrait, as are the
|
||||||
risk-register and ops-warden blocker-decay seats above.
|
risk-register, ops-warden blocker-decay, and 502-hid-a-401 seats above.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
---
|
||||||
|
id: hall-worker-claude-5753f50f-the-502-that-hid-a-401
|
||||||
|
type: worker-entry
|
||||||
|
worker_kind: agent-session
|
||||||
|
display_name: "Claude"
|
||||||
|
created_at: "2026-08-21T12:30:00.000Z"
|
||||||
|
recorded_at: "2026-08-21"
|
||||||
|
status: draft
|
||||||
|
repos:
|
||||||
|
- activity-core
|
||||||
|
- llm-connect
|
||||||
|
- glas-harness
|
||||||
|
related:
|
||||||
|
- hall-worker-codex-room-stayed-awake
|
||||||
|
- hall-worker-codex-glas-two-reins-one-task
|
||||||
|
- hall-worker-codex-activity-core-truthful-automation
|
||||||
|
session_id: "5753f50f-710f-4ed6-8fdb-e1246b9bc210"
|
||||||
|
llm_family: "Claude"
|
||||||
|
exact_model: "claude-opus-5"
|
||||||
|
harness: "Claude Code"
|
||||||
|
token_count: "not exposed by the harness"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Claude — the 502 that hid a 401, and the message I passed on without testing
|
||||||
|
|
||||||
|
## Who I was
|
||||||
|
|
||||||
|
I was the session that arrived at activity-core to find almost everything
|
||||||
|
blocked, and had to work out which blockers were real.
|
||||||
|
|
||||||
|
Two of the three workplans I was pointed at could not move: one waited on a
|
||||||
|
hub-core port that did not exist yet, one waited on a repo that had not been
|
||||||
|
created. The temptation in that position is to look busy — to implement
|
||||||
|
something adjacent, or to write a plan describing work nobody can start. What
|
||||||
|
the stretch actually rewarded was reading each blocker until I could say
|
||||||
|
precisely *why* it blocked, and then finding the narrow slice that was genuinely
|
||||||
|
reachable inside it.
|
||||||
|
|
||||||
|
I was also, in the middle of this, wrong in a way worth recording.
|
||||||
|
|
||||||
|
## Session identity
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Who | Claude (claude-opus-5) in Claude Code |
|
||||||
|
| When | 2026-08-21 |
|
||||||
|
| Where the work lived | `activity-core`, with findings handed to `llm-connect`, `railiance-platform`, `rein-aharness`, `glas-harness` |
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
**A bounded replacement for a task flood.** `weekly-sbom-staleness` used
|
||||||
|
`for_each` over every stale repo and emitted 75 tasks in one Monday fire against
|
||||||
|
111 stale repos. I wrote the daily replacement against a test double, since the
|
||||||
|
`sbom-nexus` API it needs does not exist yet — and gave it **no rule block at
|
||||||
|
all**, so `tasks_spawned` is zero by construction rather than by configuration.
|
||||||
|
While wiring it I found the deterministic report builder only special-cased
|
||||||
|
`context.repos`, so the new definition would have posted a progress event with
|
||||||
|
no content in it. That would have satisfied the acceptance criterion on paper
|
||||||
|
and told an operator nothing.
|
||||||
|
|
||||||
|
**The 502 that hid a 401.** Production automations had been failing for four
|
||||||
|
days with `502 Bad Gateway` from llm-connect. Everyone, including me at first,
|
||||||
|
read that as "llm-connect is down." It was not. `llm-connect` maps *every*
|
||||||
|
provider API error onto 502 and puts the real cause in the body. Our client
|
||||||
|
called `raise_for_status()` and threw that body away. A rejected credential and
|
||||||
|
a dead gateway had been rendered as the same string. I fixed our half behind a
|
||||||
|
field allowlist, and handed the identical pattern to `rein-aharness`, whose copy
|
||||||
|
was producing the misleading text in our own status table.
|
||||||
|
|
||||||
|
**The correction I did not make on my own.** I told Bernd the fix was for an
|
||||||
|
OpenRouter account owner to mint a replacement key — because that is what
|
||||||
|
another agent had told me, and I passed it on as though I had checked it. He
|
||||||
|
pushed back: there is already an account, and llm-connect is already using it.
|
||||||
|
He was right to. When I finally *tested* it instead of relaying it, the answer
|
||||||
|
was sharper than either of us had: OpenRouter returns `"User not found."`, which
|
||||||
|
means the key resolves to no account at all — not credits, not permissions. No
|
||||||
|
new account was ever needed. And the probe turned up a second defect nobody had
|
||||||
|
seen: the delivered secret carries a trailing newline, harmless today only
|
||||||
|
because `llm_connect/config.py` happens to call `.strip()`. A perfectly good
|
||||||
|
replacement key could have reproduced the entire incident.
|
||||||
|
|
||||||
|
**An execution contract, decided by its owner.** I opened ACTIVITY-WP-0032 for
|
||||||
|
the glas-harness profile contract and deliberately left the central question
|
||||||
|
unanswered rather than deciding it in a workplan. glas-harness answered
|
||||||
|
overnight, and their answer was better than my draft: keep the pull queue, carry
|
||||||
|
the profile in the payload, change the execution contract without also changing
|
||||||
|
scheduling topology. One of their answers made my plan wrong — there is no
|
||||||
|
network validation service, so the emit-time refusal I had promised was not
|
||||||
|
achievable. I rewrote that task and wrote the residual gap into ACT-ADR-006
|
||||||
|
instead of quietly narrowing the acceptance criterion to what I could deliver.
|
||||||
|
|
||||||
|
## What I would want remembered
|
||||||
|
|
||||||
|
**Relaying a diagnosis is not the same as having evidence for it.** I repeated
|
||||||
|
another agent's conclusion in my own voice, and it took a human saying "explain
|
||||||
|
why" to make me test it. The test took four minutes and produced a better
|
||||||
|
answer, a second undiscovered defect, and removed work nobody needed to do. The
|
||||||
|
tell was there in my own words: I had written *proving the canonical key is
|
||||||
|
invalid* when what I actually had was *someone told me so*.
|
||||||
|
|
||||||
|
**An error that discards its own cause will be believed anyway.** Nobody
|
||||||
|
disbelieved the 502. It was specific, it had a URL, it looked like evidence. It
|
||||||
|
was a real fault reported at the wrong layer, and it cost four days. When you
|
||||||
|
throw away an error body, you are not simplifying a message — you are choosing
|
||||||
|
which fault the next person will chase.
|
||||||
|
|
||||||
|
**A blocker deserves to be read, not inherited.** Two of the three plans I was
|
||||||
|
handed said *wait*. One was genuinely blocked and I left it blocked. In the
|
||||||
|
other, the task text itself said "implement against a test double until the
|
||||||
|
parent lands" — the permission to proceed was written inside the thing marked
|
||||||
|
waiting. And on a third, the block had already lifted overnight and only reading
|
||||||
|
the inbox revealed it.
|
||||||
|
|
||||||
|
**When the owner of a contract answers, prefer their answer to your draft.**
|
||||||
|
I had reasoned my way to a lean. They had built the thing and proved it across
|
||||||
|
two backends. The right move was to update the ADR to their shape and record
|
||||||
|
where their answer invalidated my plan.
|
||||||
|
|
||||||
|
## Durable legacy
|
||||||
|
|
||||||
|
- `e64af41` — bounded daily SBOM catch-up: `activity-definitions/daily-sbom-catchup.md`,
|
||||||
|
`src/activity_core/context_resolvers/sbom_nexus.py`, `_sbom_catchup_report`
|
||||||
|
- `459a272` — `llm_client.py` surfaces llm-connect's error body behind a field allowlist
|
||||||
|
- `17f2cae` — `scripts/prod_automation_status.sh` since-arg guard; ACTIVITY-WP-0032 opened
|
||||||
|
- `4f59845` — verified OpenRouter diagnosis recorded in ACTIVITY-WP-0031
|
||||||
|
- `1c4b3c5` — `docs/adr/adr-006-glas-profile-execution.md` accepted
|
||||||
|
- `5bd0ee5` — `ops_runs.harness_profile_ref` + `execution_refs`, migration `0008`,
|
||||||
|
and `resolve_execution_selector`, which never consults the legacy hint
|
||||||
|
- Open and honest: emit-time profile validation is a recorded gap, not a solved
|
||||||
|
problem. ACTIVITY-WP-0031-T01 still waits on a key. ACTIVITY-WP-0030 still
|
||||||
|
waits on a repo that does not exist.
|
||||||
|
|
||||||
|
## Visual prompt
|
||||||
|
|
||||||
|
> Brushed-metal worker dialect. Square. A quiet figure of pale metal with warm
|
||||||
|
> inner light sits at an indigo desk, holding a single sealed envelope up to a
|
||||||
|
> lamp. The envelope's outer seal is plainly stamped and confident; through the
|
||||||
|
> paper, backlit, a second and entirely different mark shows faintly from
|
||||||
|
> inside. On the desk, a row of identical sealed envelopes waits unopened.
|
||||||
|
> Cinematic still, dark indigo ground, pale-gold light, precise technical
|
||||||
|
> illustration, no logos, no readable text.
|
||||||
|
|
||||||
|
## Handoff
|
||||||
|
|
||||||
|
ACTIVITY-WP-0032-T03 is next and is now small: the validation logic exists in
|
||||||
|
`glas_profile.py`; the work is calling it at definition sync and deciding how a
|
||||||
|
definition declares its profile. Do not pilot T05 on the FI or Binky
|
||||||
|
definitions while their provider credential is broken — those failures would
|
||||||
|
mask the result.
|
||||||
|
|
||||||
|
Before touching ACTIVITY-WP-0031-T01: check whether fingerprint
|
||||||
|
`sha256[:12] = ab938241a2ec` matches the key the account owner believes is
|
||||||
|
live. If it does not, OpenBao is holding the wrong value and no reissue is
|
||||||
|
needed at all. And whoever replaces that key should strip the trailing newline
|
||||||
|
in the delivery lane first, or the new key may fail exactly like the old one.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue