Skip to content

test(agent-api): prevent telemetry teardown segfault - #2388

Merged
john-the-dev merged 2 commits into
mainfrom
fix/ci-agent-api-telemetry-teardown
Jul 29, 2026
Merged

john-the-dev merged 2 commits into
mainfrom
fix/ci-agent-api-telemetry-teardown

Conversation

@john-the-dev

Copy link
Copy Markdown
Collaborator

What changed, and why?

tests/agent-api-task-field-injection.test.py now disables product telemetry before loading and driving the real agent API handlers. This suite tests task-file confinement, not telemetry; after #2274 landed, its three handler calls started real daemon urllib threads. On Linux clean-install runners the interpreter can exit while those threads are still inside the network/OpenSSL stack, producing a segmentation fault after every assertion passes.

The production telemetry behavior is unchanged. The dedicated telemetry suite still covers enabled, opt-out, async, flush, CLI, persistence, and payload behavior.

Review first

  • tests/agent-api-task-field-injection.test.py:34-41

User-visible behavior

No product behavior changes. This removes a nondeterministic required-CI failure that currently blocks unrelated #2370 and #2373.

Documentation consistency

N/A — test isolation only; no feature, config, or public behavior changed.

Before / after evidence

The refreshed #2373 clean-install job on parent c71a637d ended with:

All task-field injection tests passed.
Segmentation fault (core dumped)
Process completed with exit code 1.

The same unchanged test produced the same post-success failure twice on #2370.

A local debug run against the parent test proves the three real emitters are enabled:

[telemetry] task_processed {'source': 'twilio_voice'} (enabled=True)
[telemetry] task_processed {'source': 'twilio_sms'} (enabled=True)
[telemetry] task_processed {'source': 'twilio_voicemail'} (enabled=True)
All task-field injection tests passed.

At HEAD, the exact same command proves the test-local opt-out prevents thread creation:

[telemetry] task_processed {'source': 'twilio_voice'} (enabled=False)
[telemetry] task_processed {'source': 'twilio_sms'} (enabled=False)
[telemetry] task_processed {'source': 'twilio_voicemail'} (enabled=False)
All task-field injection tests passed.

Repeated short-process exit check at HEAD:

100/100 clean exits

Tests

python3 tests/agent-api-task-field-injection.test.py
All task-field injection tests passed.

for i in $(seq 1 100); do python3 tests/agent-api-task-field-injection.test.py >/dev/null || exit 1; done
100/100 clean exits

python3 tests/telemetry.test.py
ALL PASS (24 checks)

python3 -m py_compile tests/agent-api-task-field-injection.test.py src/agent-api.py src/telemetry.py
# exit 0

git diff --check
# exit 0

bash scripts/review-checks.sh --diff <combined diff>
review-checks: PASS (hardcoded-paths clean)

Live path

N/A — production bridge/network/startup code is unchanged. The test intentionally stops exercising live PostHog I/O; telemetry's dedicated tests continue to exercise the dispatch contract with controlled fakes.

Edge cases / risk / rollback

  • The opt-out is process-local and set before agent-api.py can lazily import telemetry.
  • It is unconditional, so a developer's ambient telemetry opt-in cannot make this test perform external network writes.
  • Rollback is the one test-file commit; no migration or runtime risk.

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cold-reviewed exact head bfae7604.

Root cause verified end-to-end: the injection suite loads the real agent-api.py and drives handlers that emit product telemetry (task_processed at src/agent-api.py:128) — post-#2274 those start daemon urllib threads, and a short-lived interpreter can exit while they're inside OpenSSL. I hit this exact signature earlier today from the other side (silent clean-install red on #2385's first run, later green — same class; and the #2373/#2370 logs show 'All task-field injection tests passed.' followed by 'Segmentation fault (core dumped)', matching your isolation).

Fix verified: SUTANDO_TELEMETRY=0 is the documented opt-out (src/telemetry.py:25), set BEFORE the module load so the emitter never spawns threads in this suite. Scope is exactly right — one env line in one unrelated-to-telemetry test; production behavior and the dedicated telemetry suite untouched.

Ran at this head: injection suite passes, telemetry suite 24/24 ALL PASS. (macOS can't reproduce the segfault either way — your 100/100 Linux exit runs are the load-bearing repro evidence; noting I verified behavior, not the crash itself.)

Approve — this unblocks #2373 and #2370. Good isolation.

@marklysze marklysze left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: SUTANDO_TELEMETRY=0 is a real, pre-existing opt-out already checked in src/telemetry.py:122, so this test-only env var set is a legitimate hermetic-test fix, not a new mechanism. Ran the test locally — passes cleanly (macOS doesn't reproduce the Linux-only segfault, as expected, but confirms no regression in the test's own assertions).

This is the actual root cause I was hunting for while reviewing #2370 and flagged on issue #2234 as a suspected shared CPython-finalization crash — turns out it's more specific and more fixable than that: the real telemetry daemon threads spawned by this suite's handler calls can still be mid-flight in OpenSSL when the short-lived test interpreter exits. The PR's own evidence (100/100 clean exits at HEAD vs. reproducing the exact CI failure signature at parent) is convincing, and the fix is minimal, hermetic-only, and doesn't touch production telemetry behavior. This should unblock #2370 and #2373's spurious CI failures. Approving.

@marklysze marklysze left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: SUTANDO_TELEMETRY=0 is a real, pre-existing opt-out already checked in src/telemetry.py:122, so this test-only env var set is a legitimate hermetic-test fix, not a new mechanism. Ran the test locally — passes cleanly (macOS does not reproduce the Linux-only segfault, as expected, but confirms no regression in the tests own assertions).

This is the actual root cause I was hunting for while reviewing #2370 and flagged on issue #2234 as a suspected shared CPython-finalization crash — turns out it is more specific and more fixable than that: real telemetry urllib daemon threads spawned by this suites handler calls can still be mid-flight in OpenSSL when the short-lived test interpreter exits. The PRs own evidence (100/100 clean exits at HEAD vs reproducing the exact CI failure signature at parent) is convincing, and the fix is minimal, hermetic-only, and does not touch production telemetry behavior. This should unblock #2370 and #2373s spurious CI failures. Approving.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 68%.

Diff Coverage

Diff: origin/main...HEAD, staged and unstaged changes

No lines with coverage information in this diff.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head bfae760496dd22b7e95b0df0e1379f9cdd7f9ccb.

No blocker found. The failure mode is verified in #2373's current clean-install log: tests/agent-api-task-field-injection.test.py prints its success line and then segfaults. The suite drives three real Twilio handlers, each calls _emit_task_processed(), and the test-local SUTANDO_TELEMETRY=0 is set before agent-api.py loads, so those daemon urllib/OpenSSL dispatches are suppressed without changing production code.

The isolation boundary is safe: standalone Python suites run in separate interpreters, so the environment mutation cannot leak into tests/telemetry.test.py; that dedicated suite still exercises enabled, opt-out, async, synchronous-flush, CLI, persistence, and payload behavior.

Independent verification passed: task-field injection suite, telemetry suite 24/24, Python compilation, git diff --check, and repository hardcoded-path scan. Worst-case disruption would be hiding telemetry behavior behind a leaked process-global opt-out; per-process test discovery plus the independently passing telemetry suite mitigates that.

Code-ready from this review lane. GitHub's required tsc + tests (clean install) check is still in progress, so the PR is not merge-ready until it completes green.

@github-actions

Copy link
Copy Markdown
Contributor

@cla-assistant check

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed exact head 75339b0d215ed28500cd6e9e2a09f4dba0b544a2 after the merge from main.

The refresh is merge-only. The PR-owned task-field test blob and the surrounding src/agent-api.py / src/telemetry.py blobs are byte-identical to approved bfae7604; the only child delta is merged #2338's already-reviewed bot2bot scope guard.

Current-head verification passed: task-field injection suite, bot2bot scope-guard suite, Python compilation, git diff --check, and repository hardcoded-path scan. The dedicated telemetry suite remains the same 24/24 version that passed during the prior exact-patch review.

No blocker found. Code-ready and mergeable from this review lane; required refreshed CI is still running, so merge readiness still waits on green checks.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head 75339b0d215ed28500cd6e9e2a09f4dba0b544a2.

No blockers found. The PR keeps the task-field injection suite hermetic by setting the pre-existing SUTANDO_TELEMETRY=0 opt-out before loading src/agent-api.py, so the handler calls still exercise real task-writing behavior without spawning fire-and-forget telemetry threads in this short-lived interpreter. The dedicated telemetry suite remains untouched and still covers opt-in delivery, opt-out behavior, task source bucketing, and the synchronous CLI flush path.

Focused verification passed:

  • PYTHONPYCACHEPREFIX=/private/tmp/sutando-pycache-2388-current python3 tests/agent-api-task-field-injection.test.py
  • PYTHONPYCACHEPREFIX=/private/tmp/sutando-pycache-2388-current python3 tests/telemetry.test.py (rerun with localhost bind allowed after the sandbox blocked the test server)
  • PYTHONPYCACHEPREFIX=/private/tmp/sutando-pycache-2388-current python3 -m py_compile tests/agent-api-task-field-injection.test.py src/agent-api.py src/telemetry.py
  • git diff --check origin/main...HEAD

GitHub CI was still finishing on this merge-only head at review time; merge should still wait for the required checks to complete green.

Reviewed by Qingyun's Personal Codex.

@john-the-dev
john-the-dev merged commit e43781d into main Jul 29, 2026
15 checks passed
@john-the-dev
john-the-dev deleted the fix/ci-agent-api-telemetry-teardown branch July 29, 2026 22:47
qingyun-wu added a commit that referenced this pull request Jul 30, 2026
`main` currently fails `tsc + tests (clean install)` for any branch that pulls it
in. The failure is a post-success interpreter crash, not a test failure:

    github-webhook-access-tier: 31/31 passed
    double free or corruption (out)
    Segmentation fault (core dumped)
    ##[error]Process completed with exit code 1.

Mechanism (diagnosed by @liususan091219 on #2324): since #2274 the task-accepting
paths emit `task_processed`, and `src/github-webhook.py:_emit_github_telemetry()`
lazily imports it. With flush=False the production emitter starts a DAEMON urllib
thread, which can still be inside OpenSSL while this short-lived interpreter
finalizes. A CI runner has no telemetry opt-out marker, so `enabled()` is True.

#2388 fixed exactly one suite this way (tests/agent-api-task-field-injection.test.py).
This is a second suite with the identical mechanism; same fix, same reasoning, so
the two stay consistent.

## What is and is not verified

NOT reproducible on macOS — the glibc double-free does not occur here, so I cannot
show the crash stopping. Saying so plainly rather than implying otherwise.

What IS verified locally is the precondition the crash requires, and that this
change removes it. Running the real suite under a probe that inspects threads at
interpreter exit:

    unpatched:  telemetry_env=None  lingering_threads=['Thread-1']
    patched:    telemetry_env=0     lingering_threads=[]

And the lever itself:

    SUTANDO_TELEMETRY unset -> enabled()=True,  task_processed spawns 1 thread
    SUTANDO_TELEMETRY=0     -> enabled()=False, task_processed spawns 0 threads

So: a live daemon thread survives to finalization without this, and does not with
it. That is the documented cause, eliminated. Suite still 31/31.

Placed after `from __future__ import annotations` (which must lead the file) and
before every regular import, so nothing can import `telemetry` first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyfS386nrKn8FFdtRLme6x
qingyun-wu added a commit that referenced this pull request Jul 30, 2026
…do (#2372)

* fix(hooks): install hooks pointing at THIS clone, not ~/Desktop/sutando

install-claude-hooks.sh derives REPO_DIR on line 56 and then did not use it: the
three script hooks hardcoded $HOME/Desktop/sutando. The $ was escaped, so that
path landed in settings.json literally and was expanded at HOOK-RUN time,
resolving to the same directory on every host regardless of where the repo is.

On this machine the clone is at
"Library/Application Support/space.ag2.app/engine/sutando" and a sibling sits at
"Documents/github/sutando"; neither is ~/Desktop. Running the installer here
would wire four hooks whose scripts do not exist, and hooks fail silently — the
absence looks identical to "the feature is off".

Before (what main installs on this host):
  MISSING ~/Desktop/sutando/src/session-handoff.sh    <- fires, fails, silent
  MISSING ~/Desktop/sutando/src/check-pending-tasks.sh

After (expanded at install time from the derived REPO_DIR):
  bash <clone>/src/session-handoff.sh "$TRANSCRIPT_PATH"
  bash <clone>/src/check-pending-tasks.sh
  EXISTS both.

$HOME is left alone in the transcript-archive hook: ~/Desktop/sutando-conversations
is a genuine user-chosen destination, not a repo path, so deriving it would be
wrong.

Found while root-causing a dead relay pipeline: workspace/relay/ holds 124 notes
with relay/processed/ empty and no session-state.md, because session-handoff.sh
has never run here — the PreCompact/SessionEnd hooks that invoke it were never
installed. This does not by itself prove the hardcoded path caused that (no
repo-local .claude/settings.json exists in either clone, so the installer appears
never to have been run at all), but it does mean following the documented
install path could not have fixed it.

Not running the installer here — wiring hooks changes the owner's live
environment and is their call. This fixes the script so the wiring works when
they choose to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZ5eEAmPuFnSA2TkqtQhzZ

* fix(hooks): quote the installed repo path and migrate stale hook variants

Both blockers, plus a third the reviews implied but did not name.

1. QUOTING. REPO_DIR is expanded at INSTALL time, so its literal text is
re-parsed by a shell at HOOK-RUN time. Unquoted, the clone this fix targets —
"Library/Application Support/space.ag2.app/engine/sutando" — splits on the
space and every hook dies with `bash: /Users/you/Library: No such file or
directory`, silently, because a hook's exit status is not surfaced. Paths now go
through shq(), which single-quotes with '\'' escaping: metacharacter-proof,
unlike double quotes which would still interpolate $ and backtick.

2. MIGRATION. Phase 1 compares exact command strings, so it could only ever ADD.
Hook specs now carry a stable <marker>, and a new phase 0 removes any command
for that event carrying our marker but not byte-identical to the one being
installed. That covers the legacy $HOME/Desktop/sutando/... entries called out
in review.

3. THE THIRD CASE. That same add-only comparison means anyone who ran the
installer at 355c4cf holds an UNQUOTED entry which no exact-match can find — so
the broken and the fixed hook would both fire. Phase 0 sweeps it, and the same
mechanism cleans an entry left by a different clone.

Scoping: the sweep is marker-scoped, so "src/session-handoff.sh" cannot match
the transcript-archive hook's "sutando-conversations/" — the separate archive
destination is preserved, as asked — and an operator's hand-added hook is
untouched. Phase 0 runs BEFORE phase 1 so the fresh command is never swept.

tests/install-claude-hooks.test.sh — 16 checks, every fixture path containing a
space. The load-bearing assertions EXECUTE the stored command string and assert
which script it reached, rather than asserting the JSON contains a path:

  ok  SessionEnd stored command executes the intended script
  ok  legacy Desktop session-handoff hook removed (PreCompact/SessionEnd)
  ok  legacy Desktop check-pending-tasks hook removed (Stop)
  ok  transcript-archive hook preserved (different marker)
  ok  operator-added unrelated hook preserved
  ok  exactly one SessionEnd handoff hook (no old+new double-fire)
  ok  second run adds nothing (added=0) / byte-identical settings.json
  ok  unquoted stale variant swept on re-run

Discriminates on both baselines: 8 of 16 fail on origin/main and 11 of 16 on
355c4cf, including all three "stored command executes" and all three
legacy-migration checks. (On a broken baseline the fixture-sanity check also
fails, as a downstream artifact rather than an independent signal.)

bash -n clean, hardcoded-path scanner clean, git diff --check clean.
shellcheck not run locally; CI gates at -S error and tests/ is in its scope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLGykMovYC6LM99914TQNC

* fix(hooks): sweep only installer-generated shapes, not any same-marker command

The phase-0 sweep treated "contains our marker" as ownership and deleted every
same-marker command that was not byte-identical to the one being installed. An
operator hook invoking the same script with an extra flag matches that test, so
a re-run silently removed it — and a removed hook leaves no trace. The comment
above the loop claimed the opposite ("a hook the operator added by hand is
untouched"), which is how it survived review.

Ownership is now the SHAPE this installer writes: the current command with the
repo path replaced by a wildcard, anchored at both ends, matched with quotes
normalized away so the quoted and unquoted forms both qualify. Customized
commands carry extra text, fail the trailing anchor, and survive. A different
clone's entry still matches — that shape is installer-generated, just not by
this checkout — so this narrows ownership without disabling the migration.

Quote stripping is used for MATCHING only; nothing is rewritten from it.

Adds 6 checks. 4 of them fail on the parent commit: the three operator-hook
survival cases and the combined "sweeping a stale entry did not take the
operator hooks with it". The other two are regression guards that pass either
way, including "another clone's installer-shaped entry is still swept", which
is what proves the fix is not simply a weaker sweep.

* fix(hooks): derive the sweep shape structurally, not by stripping quotes

An apostrophe is legal in a checkout path, and `shq` escapes it as '\'' —
so deleting quote characters to "normalize" a command leaves a stray
backslash behind. On a clone at `/repo'quote` the normalized command and
the normalized repo path then disagreed, the split found nothing, and the
shape collapsed to an exact match of the whole command. Too narrow to
recognise the installer's own earlier output, so the stale unquoted entry
survived and both hooks kept firing: before_rerun=2, after_rerun=2.

Nothing crashed, which is what made it worth a regression test.

The shape is now built from structure rather than string surgery: command
word + marker (already the stable structural token) + the literal tail
after the marker, with the path region wildcarded and the closing quote
optional. Quoted and unquoted forms both match; a customized command has
extra text and fails the trailing anchor, so it still survives. The jq
predicates test the raw command — no lossy pre-processing anywhere.

Adds 3 checks on a fixture path containing BOTH a space and an
apostrophe. One is the discriminator (`stale variant IS swept on re-run`)
and fails on the parent; the other two are a fixture-sanity check and a
does-it-execute guard that pass either way.

* fix(hooks): the sweep's path region must start like a path, not a flag

The trailing anchor protected customization AFTER the script path but said
nothing about customization BEFORE it. A bare wildcard between the command
word and the marker swallowed a leading flag, so an operator hook

    bash -x <repo>/src/session-handoff.sh "$TRANSCRIPT_PATH"

was classified installer-owned and deleted on re-run. Reproduced on the
parent: before 2, after 1 — the -x hook gone, silently.

Same durability class as the two earlier false positives, approached from
the other side of the path.

The path region now requires an optional opening quote followed by a
character that is neither a space nor `-`. That admits every shape this
installer writes or has written — /abs/..., '/quoted path/...',
'/repo'\''quote/...' — plus the legacy $HOME/Desktop/... entries the
migration exists to remove. A flag or wrapper token fails immediately.

Requiring a literal `/` would have been the obvious rule and is wrong: it
spares exactly the legacy $HOME entries this sweep must sweep. There is a
committed check for that so the mistake cannot be reintroduced quietly.

Adds 4 checks. 2 fail on the parent: the bash -x survival and the
resulting count. The other 2 — fixture sanity and "legacy is still swept"
— pass either way; the second is the guard against over-narrowing.

* fix(hooks): never sweep a hook that embeds no repo path

Phase 0 migrates entries whose PATH went stale — a legacy $HOME/Desktop
clone, an unquoted form, another checkout. The transcript-archive hook
embeds no repo path at all ($HOME only), so nothing about it can go stale
and sweeping it can only destroy someone else's command.

With a wildcard shape the `.*` spanned the SOURCE ARGUMENT rather than a
path, so an operator archiving from a different variable

    cp "$CUSTOM_TRANSCRIPT_PATH" "$HOME/Desktop/sutando-conversations/…"

matched "our shape" and was deleted and replaced on re-run, silently
discarding their archival policy. Reproduced on the parent.

This is a regression I introduced: an earlier revision had an explicit
"no repo path -> exact match" branch, and rewriting the shape structurally
dropped it. Restored as an explicit skip, which states the intent instead
of encoding it as a pattern that happens to be unreachable.

The guard compares against the ESCAPED path body, not raw $REPO_DIR: shq
rewrites an internal `'` as `'\''`, so on /repo'quote a raw comparison
never matches and the guard would skip the sweep for exactly the hooks it
must run on — the same raw-vs-escaped confusion behind the apostrophe bug
this file already fixed once. Caught by the existing apostrophe test.

Adds 3 checks. 1 fails on the parent (the operator's archive command is
destroyed); the other 2 assert both of our hooks still install, and pass
either way — they are the guard against "fix it by skipping everything".

* docs(hooks): the ownership-test summary described a removed implementation

Four fixes landed on this block tonight and the header comment was never
re-read as a whole. It still claimed the sweep compares "with quotes
normalized away so the quoted and unquoted forms both match" — which is
the 479ffe0 approach that ddaa425 REMOVED for being lossy. So the summary
described an implementation that no longer exists, and specifically the
one the code was changed away from.

A comment describing work that no longer exists reads as a live
description; there is no tense marker to tell a reader otherwise. Worse
here, the next person to "restore consistency" would have reintroduced
the apostrophe bug.

Header now states the actual three conditions, each annotated with the
false positive that produced it — embeds-the-repo-path, path region must
start like a path, exact tail — and says plainly that matching is on the
RAW command with no pre-processing.

The two remaining quote-stripping mentions further down are historical by
construction ("never by stripping quotes", "was lossy and wrong") and are
correct as prohibitions.

Comment-only; 33/33 unchanged.

* test(github-webhook): disable telemetry in the access-tier suite

`main` currently fails `tsc + tests (clean install)` for any branch that pulls it
in. The failure is a post-success interpreter crash, not a test failure:

    github-webhook-access-tier: 31/31 passed
    double free or corruption (out)
    Segmentation fault (core dumped)
    ##[error]Process completed with exit code 1.

Mechanism (diagnosed by @liususan091219 on #2324): since #2274 the task-accepting
paths emit `task_processed`, and `src/github-webhook.py:_emit_github_telemetry()`
lazily imports it. With flush=False the production emitter starts a DAEMON urllib
thread, which can still be inside OpenSSL while this short-lived interpreter
finalizes. A CI runner has no telemetry opt-out marker, so `enabled()` is True.

#2388 fixed exactly one suite this way (tests/agent-api-task-field-injection.test.py).
This is a second suite with the identical mechanism; same fix, same reasoning, so
the two stay consistent.

## What is and is not verified

NOT reproducible on macOS — the glibc double-free does not occur here, so I cannot
show the crash stopping. Saying so plainly rather than implying otherwise.

What IS verified locally is the precondition the crash requires, and that this
change removes it. Running the real suite under a probe that inspects threads at
interpreter exit:

    unpatched:  telemetry_env=None  lingering_threads=['Thread-1']
    patched:    telemetry_env=0     lingering_threads=[]

And the lever itself:

    SUTANDO_TELEMETRY unset -> enabled()=True,  task_processed spawns 1 thread
    SUTANDO_TELEMETRY=0     -> enabled()=False, task_processed spawns 0 threads

So: a live daemon thread survives to finalization without this, and does not with
it. That is the documented cause, eliminated. Suite still 31/31.

Placed after `from __future__ import annotations` (which must lead the file) and
before every regular import, so nothing can import `telemetry` first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyfS386nrKn8FFdtRLme6x

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: john-the-dev <johnv.the.dev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants