Skip to content

feat: lg-handoff, distil and privately share an agent session - #3

Merged
datj9 merged 16 commits into
mainfrom
feat/handoff
Aug 22, 2026
Merged

datj9 merged 16 commits into
mainfrom
feat/handoff

Conversation

@datj9

@datj9 datj9 commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Implements Feature 1 of the "Team fabric for agent CLIs" brainstorm: hand a
colleague enough context to continue your work without handing them your
transcript.

lg-handoff is a second bin. It distils one claude/codex/opencode session into a
four-file bundle, scans it for secrets, and publishes it privately to enclave
behind a time-boxed share link.

lg-handoff pack claude --cwd . --title "auth refactor, where I got to"
lg-handoff scan ./handoff-bundle
lg-handoff push ./handoff-bundle --expires 7d

Why the plan changed before any code was written

The brainstorm was reviewed against the actual enclave CLI contract and this
repo. Three of its Feature-1 designs turned out to be unbuildable as written:

  • The bundle layout had no index.html. enclave push requires it at the
    root - it is the page served. The push would have been refused outright.
  • The bundle.tar workaround is impossible. .tar is not in enclave's
    13-extension allowlist. Not awkward - refused.
  • raw/ transcript upload is dead on arrival. .jsonl is not an allowed
    extension, and the plan's own 3.8 MB example transcript exceeds the 2 MB
    per-file cap. This is why v1 is distilled-only: raw mostly cannot ride
    enclave.

Also dropped: handoff pull, because enclave has no fetch subcommand and a
share URL is print-once (the server keeps only its hash).

checkEnclaveConstraints now encodes that whole contract locally, so a bad
bundle fails with the limit named instead of as an opaque server refusal.

What is deliberately not here

All of Feature 2 - the daemon, Redis streams, Ed25519 envelopes, the approval
gate, crossagent integration. It directly contradicts README.md: "Not a
workflow server. No daemon, no web UI, no cloud." An inbox that can start an
agent on someone else's laptop is a different product with a much harder threat
model, and it belongs in a sibling repo.

To keep that option cheap, src/handoff/** imports nothing from src/core/**
or src/adapters/**. Extracting it later is a git mv. That is also why
buildEnclavePushArgs exists twice on purpose - AGENTS.md now says not to
deduplicate it.

Security posture

A transcript is production data, so the code treats it that way:

  • push is fail-closed and ordered. Non-private visibility refused first,
    then the secret scan, then the enclave constraint check. A failure at either
    gate means enclave is never invoked - asserted with a fake Exec recording
    zero calls.
  • Readers are a narrowing boundary. Codex base_instructions, world_state,
    inter_agent_communication*, Claude file-history-snapshot, tool-result
    blobs and attachments never reach the output. A test serialises the result and
    asserts base_instructions appears nowhere.
  • 10 scanner rules, each with a near-miss that must not fire - a bare eyJ
    is not a JWT, task-list is not an sk- key. Findings are masked, so a
    report cannot reproduce the secret it found.
  • index.html escapes everything. Its content derives from an untrusted
    transcript; <script>alert(1)</script> in a turn renders as literal text, and
    the page makes zero external requests.
  • private visibility only. org and public are refused.

Test plan

  • npm run typecheck - clean.
  • npm test - 312 pass (23 files), up from 197.
  • npm run build - emits dist/handoff/cli.js; lg smoke test
    (node dist/cli.js run examples/hello.yaml) still passes.
  • No test spawns claude, codex, opencode, enclave or git, and none
    touches the network. Every spawn goes through an injected Exec seam.
    AGENTS.md updated to say so.
  • Real end-to-end check against a 154 KB Claude transcript: bundle written,
    warnings surfaced (unknown record type, 16 tool-result blocks dropped), scan
    clean, and zero residual /Users/<name> paths and zero external resource
    URLs in the output.

Known gaps, stated rather than hidden

  • The enclave share create --json parser is lenient because that stdout shape
    has not been captured from a real invocation. AGENTS.md requires a fixture -
    one real run should tighten it.
  • The OpenCode export shape is unverified, so that reader is defensive and
    marked experimental.
  • Session discovery relies on an undocumented, version-dependent directory
    encoding. pack always prints which file it chose, and --session-file
    overrides a wrong pick.
  • Distillation is extractive, not summarised. The brief says so in a banner.

@gitguardian

gitguardian Bot commented Aug 21, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
36480686 Triggered Bearer Token af7e0c1 src/handoff/scan.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

datj9 added 6 commits August 21, 2026 23:23
A handoff bundle is four files with index.html at the root, because that is
what enclave serves. checkEnclaveConstraints encodes the published push limits
(13 allowed extensions, 2 MB per file, 50 files, 10 MB total) so a bad bundle
fails locally with the limit named instead of as an opaque server refusal.

The subtree is import-clean by design: nothing here reaches into src/core/ or
src/adapters/, so it can be extracted to a sibling package with a git mv.
Pure parsers that narrow a transcript to turns, session id, cwd, model and
file paths. They never throw on malformed input; a bad line becomes a warning.

The drop list is the point: codex base_instructions, world_state,
inter_agent_communication, claude file-history-snapshot records, tool-result
blobs and attachments never reach the output. A transcript is untrusted,
secret-bearing input and the reader is the narrowing boundary.

The opencode export shape is unverified against a real invocation, so that
parser is defensive and marked experimental per AGENTS.md.
Ten named rules, each with a positive fixture and a deliberate near-miss that
must not fire: a bare eyJ is not a JWT and task-list is not an sk- key. Finding
excerpts are masked, so a report can never itself reproduce the secret.

The scanner only reports. Fail-closed policy belongs to the CLI.
Distillation is extractive and mechanical: turns are quoted under fixed
headings, with a banner saying so. No model calls, so pack works offline and
the no-LLM-SDK rule holds.

index.html is the security boundary. Its input is derived from an untrusted
transcript, so every interpolated value is escaped and the page makes zero
external requests.
A second bin, not an lg subcommand: it shares no state with a run and owns its
own exit codes (0 ok, 1 usage or not found, 2 findings or push refused).

push is fail-closed and ordered. A non-private visibility is refused first,
then the scanner, then the enclave constraint check - and a failure at either
gate means enclave is never invoked at all. Tests assert that with a fake Exec
recording zero calls.

The share url is printed and written to SHARE-URL.txt in the same step,
because enclave prints it once and keeps only its hash: lose the line and the
link is unrecoverable.

Every spawn - git, opencode export, enclave - goes through an injected Exec
seam, so no test touches a real binary or the network.

The enclave share-create json parser is lenient on purpose; that stdout shape
has not been captured from a real invocation yet.
States what handoff deliberately does not do, in the voice of the existing
non-goals: no pull (enclave has no fetch and share urls are print-once), no raw
transcript upload (.jsonl is not an allowed extension and a real transcript
exceeds the 2 MB file cap), no cross-CLI replay, no session transplant, private
visibility only, and no signal bus or daemon - that would contradict 'Not a
workflow server'.

AGENTS.md gains the Exec-seam test rule and the src/handoff/ import rule.
datj9 added 3 commits August 21, 2026 23:31
All three were verified by running the exploit input, not by reading the code.

1. The git remote was published verbatim and never passed through
   redactSession, so a credential-bearing remote
   (https://oauth2:<token>@host/repo.git) landed in meta.json, handoff.md and
   index.html. It is now stripped at the point it is gathered, and the strip is
   reported as a warning. A bare user@host is deliberately left alone -
   ssh://git@github.com is the commonest remote there is and rewriting it would
   destroy the one fact the reader needs; a token used AS the username is caught
   by the vendor rules instead.

2. The scanner was a narrow vendor allowlist. These five inputs all wrote
   through with zero findings and would have been published:
     https://oauth2:<glpat>@gitlab.com/org/repo.git
     https://user:hunter2@host/repo.git
     sk_live_<stripe>            (underscore, so the sk- rule could not match)
     database_password=hunter2   (rule was upper-case only)
     postgres://admin:pw@db:5432/app
   Adds url-credentials, stripe-key, gitlab-token, npm-token and sendgrid-key;
   widens github-token to ghu_/ghr_, makes env-assignment case-insensitive and
   able to match a JSON key, and accepts a lower-case Windows drive letter. All
   five now fire.

3. A re-pack into an existing --out directory left the previous
   SHARE-URL.txt in place, and nothing downstream caught it: .txt is an allowed
   extension and no rule matches an enclave share url, so the new artifact
   served the old share link. writeBundle now purges it.

Also gitignores handoff-bundle/ and SHARE-URL.txt - the default output path is
inside the repo, so a routine git add -A committed a live capability url.

The scanner is still an allowlist of shapes, not a proof, and the README now
says so rather than implying coverage it does not have.
Two silent-success paths, both verified against the built cli before and after.

Given one bundle containing an aws key, the scanner reported:
  file readable   -> 1 scan finding, exit 2
  file chmod 000  -> scan clean,     exit 0
An empty result means "clean, safe to publish" to every caller, so a file
skipped because it was unreadable was indistinguishable from a file that was
read and found innocent. Unreadable files, unlistable directories and a
non-directory path now each produce an unreadable-file finding, so the gate
fails closed. walk() is wrapped too - it could previously throw straight out of
scanBundleDir while the surrounding code advertised that it never would.

Separately, `lg-handoff scan /typo` printed "scan clean" and exited 0. It now
reports the missing directory and exits 1, keeping "cannot find it" and "found
something in it" distinguishable at the exit code.

Also documents --visibility in the README command table (it was only mentioned
in the non-goals list) and notes that line 0 on a finding means the file itself
rather than a line in it.
The section explained what the feature is before it explained what to do with
it, listed exit codes with no matching action, and had no picture of the output -
so a reader could not tell what a colleague would actually receive.

Now leads with the two commands and the handover story, then shows the rendered
brief (captured from a real run, not invented), then reference material.

Adds what was missing rather than rewording what was there:
- what the recipient sees, and that they install nothing
- an exit-code table with a Do this column, and the fact that exit 2 from push
  means enclave was never invoked
- where each adapter's session is discovered, and why discovery is a guess
- a troubleshooting table keyed on the actual message strings, each verified to
  exist in the shipped source
- the share url being print-once, and how to hand over again or revoke

--help now carries the same workflow, exit codes and caveats, so an agent that
runs lg-handoff --help learns the tool without opening the README. pack and push
get their own help notes for session discovery and the fail-closed refusal.

AGENTS.md gains the module map and the two rules that are the point of the
subtree: readers drop rather than carry, and the gates fail closed.
@datj9 datj9 self-assigned this Aug 22, 2026

@datj9 datj9 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

(permission probe; ignore)

@datj9-reader datj9-reader 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.

Review — feat: lg-handoff, distil and privately share an agent session

Reviewed 4556106 against main. Verified locally: npm run typecheck exits 0, npm test = 23 files / 303 tests pass, npm run build emits dist/handoff/cli.js. CI green on check (22), check (24), GitGuardian. Enclave contract cross-checked against the installed enclave CLI's own push --help and share create --help.

Verdict: fix-first. Two blocking issues, both in the security boundary this feature exists to be. Everything else is small.


Blocking

H1 — push can republish the previous share URL as a served file (src/handoff/commands.ts:224-228)

writeBundle deliberately scrubs SHARE-URL.txt on every pack (bundle.ts:38-43, with a comment explaining exactly this risk). pushCommand writes that file at the end but never scrubs it at the start. So packpush is safe, but a second push on the same directory uploads the prior print-once link as a page in the new artifact.

Verified on a bundle dir containing the four files plus SHARE-URL.txt:

scan findings:            []
constraint violations:    []

Neither gate catches it: .txt is in the 13-extension allowlist, and no scan rule matches an enclave /s/… URL — which bundle.ts:41-42 already acknowledges as the reason the pack-time scrub exists. The consequence is that a link the sender believes is separately scoped becomes readable to everyone holding the new artifact's link, and there is no test for a second push.

Fix: rmSync(join(bundleDir, SHARE_URL_FILE), { force: true }) at the top of pushCommand, before scanBundleDir. Or treat the file's presence as a constraint violation. Either way, add a test that pushes twice.

H2 — rewritePaths replaces the bare username everywhere, corrupting content (src/handoff/scan.ts:290)

After the three home-path shapes are handled, replaceLiteral(out, opts.username, "user") runs against the whole document as an unanchored substring. Short or common usernames appear inside ordinary words:

username input output
dat the dataset in /srv/data was validated; datadog alerts fired the useraset in /srv/usera was valiusered; useradog alerts fired
ec ec2 instance ready, second attempt user2 instance ready, suserond attempt

This is worse than cosmetic. The brief is the artifact a colleague acts on, and this silently rewrites file paths, commands and identifiers inside the quoted turns — with no warning emitted. It also hurts the security goal it serves: the mangling is loud enough that a reader may stop trusting the redaction, and abs-home-path already catches the residue this line is trying to pre-empt.

Fix: bound the replacement to path and delimiter contexts — require a separator or word boundary ((?<=[/\\@:\s"'])(?=[/\\@:\s"']|$)), or drop the bare-username pass entirely and rely on the three explicit home shapes plus the abs-home-path rule. Add the dat/dataset case as a test.


Non-blocking

M1 — README overclaims that exit 2 means enclave was never invoked (README.md ~348-350, echoed at cli.ts:69 and cli.ts:136-140)

pushCommand also returns 2 after a failed enclave push (commands.ts:183-187) and after a failed enclave share create (commands.ts:210-214). In the share-failure path the private artifact is already publishedviewUrl is logged one line earlier. An operator reading exit 2 as "nothing left the machine" will be wrong on any auth error, network blip, or rejected --expires. Either split local-refusal from remote-failure exit codes, or reword to "exit 2 at a local gate means enclave was never invoked; exit 2 after that means see stderr, a partial publish is possible".

M2 — --expires is forwarded to enclave unvalidated (cli.ts:131, enclave.ts:45-46)

Enclave accepts 7d|12h|2w, a date, a datetime, or a zoned ISO instant; anything else is an InvalidInputError. --expires forever therefore publishes the artifact and then fails share-create, landing in the H2-adjacent partial state above with no share URL persisted. Validate the shape locally before the first spawn — the same "fail with a named limit instead of an opaque server refusal" argument that justifies checkEnclaveConstraints.

M3 — the scanner's coverage is narrower than the docs imply

The PR body says 10 rules; SCAN_RULES has 15 plus unreadable-file. That direction is fine, but the gaps matter more than the count. Probed against the real rule set, these are missed:

Shape Result
AWS secret access key (40-char) missed
Authorization: Bearer … / Basic … missed
Stripe restricted (rk_live_) missed — sk_live_ is caught
Google OAuth client secret (GOCSPX-) missed
HuggingFace token (hf_) missed
Hex-32/hex-64 client secrets missed
PEM body without its header line missed
Absolute non-home paths (/srv/clients/acme-bank/…, D:\clients\…) missed

Controls all fire correctly (sk-ant-, ghp_, glpat-, env assignment, URL credentials, /home/<user>/, PEM header, and the JWT/task-list near-misses stay quiet). Not a merge blocker — a prefix scanner is honest work and the masking is correct at 4 chars — but README/PR should describe it as best-effort with a named gap list rather than as the gate. Authorization: headers and rk_live_/hf_/GOCSPX- are cheap adds.

L1 — .svg is skipped by the scanner but allowed by enclave (scan.ts:124-131 vs types.ts:50-64)

BINARY_EXTENSIONS includes .svg, so it is never read; .svg is in enclave's 13-extension allowlist, so it is uploaded. A v1 bundle only ever contains four files, so this is unreachable today — but scanBundleDir walks recursively and checkEnclaveConstraints accepts any allowlisted file, so a hand-added SVG ships unscanned. Verified: an SVG containing sk-ant-api03-… produces zero findings. SVG is text; drop it from the binary set.

L2 — err as Error without narrowing (cli.ts:34) — a non-Error throw prints error: undefined. err instanceof Error ? err.message : String(err) matches what scan.ts already does.

L3 — stale test count — PR body says 289, suite is 303.


What holds up

Checked and clean, so it does not get re-litigated later:

  • Ordering is right. pushCommand runs visibility → scan → constraints → spawn (commands.ts:154-174). No bypass flag exists, and --visibility refuses anything but private before any spawn.
  • Fail-closed is real. An unreadable dir, an unlistable dir, and an unreadable file each become an unreadable-file finding rather than a skip (scan.ts:205-233). Reachable empty findings genuinely mean "scanned and clean" — except through the two paths in H1 and L1.
  • The renderer does not inject. Probed with hostile transcript turns: </title><script> in the title, </pre><img src=x onerror=…><pre> in a turn, <script> in a warning. All escaped, zero live <script>, and the deliberate absence of a markdown engine holds. Section spoofing also fails — a turn containing ## Warnings stays inside its <pre>; the document still has exactly one <h2>Warnings</h2>. render.test.ts:111-137 already tests this class.
  • The readers narrow rather than convert. DistilledSession has no field for tool results, attachments, snapshots, base_instructions, or permission modes, and each reader drops at the boundary: claude.ts:23-36 + :72-89, codex.ts:18-28 + :119-124, opencode.ts:73-93.
  • Layering and purity hold. No src/handoff/** file imports src/core/** or src/adapters/** (only prose in comments). scanBundleDir is the single I/O function in scan.ts; render.ts and the readers are pure; commands.ts is the only spawner, via the injected Exec; execa is confined to cli.ts. The buildEnclavePushArgs duplication is intact and commented as intentional. Zero any in non-test sources.
  • The plan-change rationale checks out against the real CLI. index.html required, exactly 13 extensions, 2 MB/file, 50 files, 10 MB total, no fetch subcommand, share URL print-once — all confirmed from enclave push --help and enclave share create --help, and all encoded in types.ts / checkEnclaveConstraints.
  • No test spawns a real binary, and the nine commits are conventional, lowercase, imperative, one logical change each.

H1 and H2 are both a handful of lines. Fix those two plus M1's wording and this is good to merge.

datj9 added 6 commits August 22, 2026 02:53
writeBundle already deletes SHARE-URL.txt so pack cannot republish a
print-once link. push wrote the file and never removed it, so a second
push on the same directory uploaded the previous /s/… url as a .txt page.
An unanchored replace of the local username turned dataset/datadog into
useraset/useradog and silently corrupted the brief a colleague acts on.
A rejected expiry used to publish the private artifact and then fail at
share create, leaving a live view url and no persisted share link.
push also returns 2 after a failed enclave push or share create. In the
share-create path the private artifact is already published.
svg is text and on enclave's allowlist, so skipping it was a hole.
Authorization headers, stripe rk_, hf_, and GOCSPX- are cheap prefix
adds. The scanner is still an allowlist of shapes, not a proof.
The new local expiry check is a usage error, same as a refused
visibility. It must not be described as the scan/constraint exit 2.
@datj9

datj9 commented Aug 22, 2026

Copy link
Copy Markdown
Owner Author

Addressed the review on 876c1c5.

H1: pushCommand now deletes SHARE-URL.txt before the scan, same as writeBundle. A leftover file is gone by the time the fake enclave push spawn runs; the new test asserts that.

H2: bare-username rewrite is delimiter-bounded (path / whitespace / quote / @ / :). dataset and datadog stay intact. Isolated author dat pushed still becomes author user pushed. dat-laptop is left alone.

M1: exit 2 docs now distinguish a local gate (nothing uploaded) from a remote failure (partial publish possible).

M2: --expires is validated locally before the first spawn. forever returns 1 and records zero exec calls.

M3 / L1 / L2: scanner described as an allowlist with a named gap list. Cheap prefixes added (rk_, hf_, GOCSPX-, Authorization: Bearer|Basic). .svg is scanned. Handoff CLI errors use instanceof Error.

L3: test count in the PR body is now 312.

Left out of this round, as the review marked them non-blocking: AWS secret access keys, PEM bodies without a header, hex client secrets, non-home absolute paths.

@datj9-reader datj9-reader 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.

Code Review

Verdict: Approve

Re-checked 876c1c5 against the previous CHANGES_REQUESTED review. Local evidence: npm run typecheck exits 0, npm test = 23 files / 312 tests pass.

Fixed since last review

  • H1: pushCommand removes SHARE-URL.txt before scanBundleDir. Test asserts the leftover file is gone at the enclave push spawn.
  • H2: username rewrite is delimiter-bounded. dataset / datadog / ec2 / second are unchanged. Isolated author dat pushed still becomes author user pushed.
  • M1: README and CLI help no longer claim every exit 2 means nothing was uploaded.
  • M2: isValidExpires rejects forever before any spawn. Invalid expiry is exit 1.
  • L1: .svg dropped from BINARY_EXTENSIONS; planted AWS key in logo.svg is now a finding.
  • L2: handoff finish narrows with instanceof Error.
  • M3 cheap prefixes: rk_live_ / rk_test_, hf_, GOCSPX-, Authorization: Bearer|Basic. Docs name the remaining gaps.

Notes

No new blocking issues. AWS 40-char secrets, PEM bodies without a header, hex client secrets, and non-home absolute paths remain out of the scanner, as documented.

A complete Authorization Bearer literal in the source tripped the
repo secret scanner. Same split used for the other fabricated shapes.

@datj9-reader datj9-reader 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-verify — prior review against 08265fc

Prior review: #3 (review) (4556106). Current HEAD: 08265fc (+7 commits). Local gates: npm run typecheck exits 0, npm test = 23 files / 312 tests pass.

ID Prior Verdict Evidence
H1 SHARE-URL re-push fixed commands.ts:174 rmSync(SHARE_URL_FILE) before scan; test at commands.test.ts:155; probe: after scrub, file gone, scan+constraints clean
H2 bare-username rewrite fixed replaceUsernameToken (scan.ts) bounds on path/whitespace/@/:/"'; dataset/datadog/ec2 preserved, path-context dat still → user; test at scan.test.ts:304-313
M1 exit-2 overclaim fixed README ~346-349 and cli.ts:139-141 now distinguish local-gate 2 from post-enclave 2 / partial publish
M2 --expires unvalidated fixed isValidExpires (enclave.ts:54-59) + gate at commands.ts:163-169 returns 1 before any spawn; forever/1m/yes/"" → false; good shapes → true
M3 scanner gaps partially addressed Added huggingface-token, google-oauth-secret, auth-header; stripe rule now covers rk_; Bearer/Basic/hf_/GOCSPX-/rk_live_ all DETECTED in probe. Still missed (disclosed-class residual): AWS secret access key body, PEM body without header, bare hex-32/64. Not a merge blocker
L1 .svg unscanned fixed .svg dropped from BINARY_EXTENSIONS (scan.ts:141-147); probe: SVG with sk-ant-…anthropic-key finding
L2 err as Error fixed cli.ts:34 now err instanceof Error ? err.message : String(err)
L3 PR body "289 tests" still valid PR body still says 289 pass (23 files); suite is 312. Cosmetic only

Count

Verdict n
fixed 6 (H1, H2, M1, M2, L1, L2)
partially addressed 1 (M3)
still valid 1 (L3)
overstated / stale 0

Decision

Both blockers are gone, with tests. Residual M3 gaps are the hard shapes a prefix scanner honestly leaves; L3 is a one-line PR-body refresh. Approve from my side — L3 is not worth holding the merge.

@datj9
datj9 merged commit fa22e66 into main Aug 22, 2026
2 of 3 checks passed
@datj9
datj9 deleted the feat/handoff branch August 22, 2026 03:09
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.

2 participants