Skip to content

Bugfix rollup (2026-06-10 session): cron, state DB, agent loop, MCP bridge, gateway, desktop, Windows - #44061

Closed
AIalliAI wants to merge 476 commits into
NousResearch:mainfrom
AIalliAI:bugfixes/claude-2026-06-10
Closed

Bugfix rollup (2026-06-10 session): cron, state DB, agent loop, MCP bridge, gateway, desktop, Windows#44061
AIalliAI wants to merge 476 commits into
NousResearch:mainfrom
AIalliAI:bugfixes/claude-2026-06-10

Conversation

@AIalliAI

@AIalliAI AIalliAI commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Rollup of the bugfixes from my 2026-06-10/11 debugging session — ~35 fixes spanning the cron scheduler, state DB, agent loop, MCP bridge, runners, gateway, CLI, Windows support, and the Desktop app.

Most fixes are also submitted as focused standalone PRs (cross-referenced below) so they can be reviewed and landed independently. This branch exists for anyone who wants the whole batch at once; if the standalone PRs land first, the corresponding commits here become no-ops and I'll keep the branch rebased. Merged with current main (post-#43956 per-job-profile revert) and conflict-free.

Related Issue

Addresses #44030, #44035, #44100, #44116, #44117, #44119, #44135, #44150 (each carries a Fixes tag in its standalone PR).

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

Cron / scheduling

  • _jobs_file_lock now covers create_job/update_job/remove_job (lost updates vs scheduler threads); malformed next_run_at no longer crashes every tick; repeat-limit auto-delete no longer leaks output dirs
  • _EnvMutationGate: workdir jobs are excluded from overlapping parallel-pool jobs that read the same process-global state
  • [SILENT] marker only suppresses delivery when leading/trailing, not mid-report; cron hint no longer mislabeled as user instruction in the skills path
  • HERMES_CRON_SESSION is now a per-job contextvar instead of a sticky process-wide env var that flipped approval semantics for later interactive sessions
  • Desktop ticker defers to the gateway scheduler owner (fix(cron): defer desktop ticker to gateway scheduler owner #44049)

State DB / persistence

Agent loop / providers

MCP bridge / runners

  • WAL-mode commits no longer stall the event bridge; incremental tail fetch instead of full-transcript rehydration per tick; mini_swe_runner reasoning capture, max-iterations warning, default model id; batch_runner exit codes; trajectory_compressor connection-pool leak

Gateway / CLI / Desktop / Windows

How to Test

  1. pytest tests/ -q
  2. Targeted suites: pytest tests/cron tests/hermes_cli tests/gateway tests/tools -q
  3. Each standalone PR linked above has its own focused repro/verification steps.

Checklist

Code

  • I've read the Contributing Guide
  • I searched for existing PRs — overlapping standalone PRs are intentionally cross-referenced above
  • My PR contains only changes related to this fix/feature (bugfix-session rollup; no unrelated commits)
  • I've run pytest tests/ -q — 6078 passed; the 34 failures in tests/tools/ fail identically on current main on this machine (environment-specific: PulseAudio/Docker/file-tool platform assumptions)
  • I've added tests for my changes
  • I've tested on my platform: macOS (Apple silicon)

Documentation & Housekeeping

  • Relevant docs/docstrings updated where behavior changed — or N/A
  • No config keys added/changed — N/A
  • No architecture/workflow changes — N/A
  • Cross-platform impact considered (includes Windows console-flash fixes)
  • Tool descriptions/schemas — N/A

@alt-glitch alt-glitch added invalid This doesn't seem right comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Jun 11, 2026
@AIalliAI AIalliAI changed the title Bugfixes/claude 2026 06 10 Bugfix rollup (2026-06-10 session): cron, state DB, agent loop, MCP bridge, gateway, desktop, Windows Jun 11, 2026
@AIalliAI

Copy link
Copy Markdown
Contributor Author

Synced with main (merge commit, no history rewrite). Two things worth flagging from the conflict resolution:

  • main reverted per-job cron profile support in 7d8d000, so this branch's cron commits were trimmed to match: dropped the profile normalization in update_job and the _job_profile_context manager, and scoped the env-mutation gate / sequential-pool docs to workdir jobs only. The scheduler-ownership deferral, env-mutation gate, and workdir handling are unchanged. Re-added the from contextlib import contextmanager import the revert had removed.
  • removed two accidentally committed node_modules symlinks (gitignore's node_modules/ matches directories only, not symlinks).

tests/cron/ passes in full (444 tests) on the merged tree.

@AIalliAI
AIalliAI requested a review from a team June 11, 2026 23:33
underthestars-zhy and others added 13 commits June 12, 2026 01:07
…atch tests

Follow-up for salvaged PR NousResearch#44486: the adapter shipped remove_reaction but
the tool only exposed 'react'. Generalize _handle_react(remove=) and add
tool-level dispatch tests for react/unreact (missing from the original PR).
…fallback

Widen the salvaged NousResearch#12851 fix to match the established classification
pattern (WhatsApp/Slack/BlueBubbles/Mattermost): video/* -> VIDEO, and
any remaining MIME type falls through to DOCUMENT instead of TEXT, so
exotic types still trigger run.py's document-context injection.
Email cached document attachments and placed them in media_urls, but
msg_type only flipped on image attachments — documents stayed TEXT and
run.py's document-context injection (gated on MessageType.DOCUMENT)
silently dropped them. Same bug class as Signal NousResearch#12845. DOCUMENT wins
over PHOTO for mixed attachments since image handling keys off per-path
mime types while document injection gates strictly on message_type.
SimpleX tagged unknown files application/octet-stream in media_types
but classification only handled audio/image, leaving msg_type TEXT —
run.py never injected the document context. Same bug class as NousResearch#12845.
Avoid recompiling the pattern on every _serialize_for_summary call; name it
beside _PATH_MENTION_RE with the NousResearch#14665 rationale.
…ousResearch#44702)

Modal prompt panels (dangerous-command approval, clarify questions)
live in the prompt_toolkit layout and vanish on the next repaint,
leaving no trace of the question or the decision in chat history.

Emit a dim one-line summary after each prompt resolves:
  ⚠ Approval: <command> → allowed for session
  ? Clarify: <question> → <answer>

Gated on display.persist_prompts (default true). Detail and outcome
are whitespace-collapsed and capped at 120 chars.
Two latent failures exposed when maintainers approved workflow runs
(every earlier run was action_required, so tests had never executed):

- _broken_imports flagged packages whose modules tests inject into
  sys.modules as spec-less stubs (types.ModuleType) — find_spec reads
  module.__spec__ on already-loaded modules and raises. Already-loaded
  means importable; short-circuit before find_spec. Broke
  test_tts_mistral, test_transcription_tools, test_daytona_environment,
  test_managed_media_gateways, test_web_tools_config, test_fal_plugin
  in CI (packages really install there, then the smoke test ran against
  the test's stub).
- TestTerminatePid taskkill fake_run lacked **kwargs and rejected the
  creationflags=windows_hide_flags() now passed in gateway/status.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AIalliAI

Copy link
Copy Markdown
Contributor Author

Thanks for approving the workflow runs — that was the first time CI actually executed on this branch, and it surfaced three things, all now resolved at 04184bd0b:

The earlier check-attribution failure was fixed in 3cc442512 by mapping the new contributor emails; #44773 proposes the same mappings on main so the other open PRs clear that check too.

The new head needs another workflow approval when you get a chance.

AIalliAI and others added 3 commits June 12, 2026 08:31
Conflict: agent/context_compressor.py — combined this branch's multimodal
text extraction with upstream's MEDIA-directive stripping (286ecd2).
…onfig.yaml

Per the AGENTS.md contribution rubric, behavioral settings belong in
config.yaml, not new HERMES_* env vars. The user-facing knob is now
network.tls_max_version (sibling of network.force_ipv4 — same
"connectivity workarounds" section), bridged onto the internal
HERMES_TLS_MAX_VERSION env var at process startup by
hermes_constants.apply_tls_max_version(), following the established
gateway.strict -> HERMES_MEDIA_DELIVERY_STRICT bridge pattern.

The env var remains the mechanism (agent/process_bootstrap has no
config access at client-build time, and spawned agent subprocesses
must inherit the cap) and an explicitly exported value still wins
over config.yaml for one-off shell overrides.

Bridged in both entrypoints that already apply network.force_ipv4:
the hermes_cli/main.py early raw-yaml block (covers CLI, desktop
dashboard spawns, TUI gateway) and the gateway/run.py bootstrap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tform-gate tests

Cherry-pick of upstream PR NousResearch#43298 (eazye19). The four
TestUpdateCommandPlatformGate gate-pass tests spawn a REAL detached
`setsid hermes update --gateway`; on CI's detached-HEAD checkout the
fallback `git reset --hard origin/main` deletes branch-added test files
minutes later, failing whichever shard is mid-collection (exit-4
"file or directory not found" flake). Carried here so the rollup CI
stops tripping on it; drop when NousResearch#43298 merges upstream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AIalliAI

Copy link
Copy Markdown
Contributor Author

CI update — runs now start without per-push approval, thanks for that.

The run at 4d7038fe came back with slices 1–3 and 5 green, confirming the 04184bd0b fixes. The two remaining failures are both flakes, not branch regressions:

  • test (2): the fix(tests): update-command platform-gate tests spawn a real detached hermes update #43298 checkout-reset flake again — the two "file or directory not found" files (test_session_table_width.py, test_44100_partial_recovery_persistence.py) are branch-added, forensics show exists=False with a clean tree, i.e. the detached hermes update spawned by the update-command platform-gate tests reset the checkout to origin/main mid-run. Since this PR adds many test files, it trips on nearly every run, so the branch now carries fix(tests): update-command platform-gate tests spawn a real detached hermes update #43298's autouse fixture as of 5ce55786b (verbatim, attributed to its author) — I'll drop it from here when fix(tests): update-command platform-gate tests spawn a real detached hermes update #43298 merges.
  • test (6): test_notification_poller_emits_distinct_watch_matches_once failed assert 3 == 2. That test is upstream and unmodified by this branch, and the assertion dump shows the extra status.update was emitted with other sessions' hex sids (0abe5e79, ec783fb7), not the test's own sid_watch_dedup: notification-poller threads leaked by earlier tests in the same pytest process race on the global process_registry.completion_queue and each has a fresh dedup set, so the exact-replay event isn't deduped across threads. Pre-existing test-isolation issue — this branch's added tests just reshuffle the slice enough to surface it. Filing it separately.

Fresh CI is running on 5ce55786b.

check-attribution flagged support@captureclient.net, introduced by
cherry-picking PR NousResearch#43298 onto this branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alt-glitch and others added 5 commits June 12, 2026 08:51
…usResearch#44776)

* feat(billing): /usage → portal top-up browser handoff

Add the terminal side of the billing slice (phase 2a): start a top-up by
throwing the user to the portal billing page with the top-up modal open. The
terminal does not confirm, poll, or track payment — checkout completes in the
browser and the next /usage shows the new balance.

- nous_account.py: parse organisation.slug/name from /api/oauth/account into
  NousPortalAccountInfo; add nous_portal_topup_url() building the org-pinned
  {base}/orgs/{slug}/billing?topup=open with a null-slug fallback to the legacy
  {base}/billing?topup=open (never /orgs/None/...).
- portal_cli.py: 'hermes portal topup' — fresh account fetch, identity line
  (Topping up as <email> / org <name>), browser open with printed-URL fallback,
  no-wait closing copy. No polling/confirmation (deferred to 2b).
- account_usage.py: the shared /usage credits block now links the org-pinned
  top-up URL (auto-opens the modal) + points to the command.

Depends on NAS NousResearch#409 (organisation.slug/name + ?topup=open). Do not merge until
that is live on the target env; until then /api/oauth/account returns
organisation: { id } only and the URL falls back to legacy.

* feat(billing): /credits command for balance + top-up handoff

Replace the standalone `hermes portal topup` subcommand with an in-session
/credits slash command — a focused money surface (balance in, top-up out) that
works in the CLI, TUI, and every messaging platform from one registry entry.

- commands.py: register /credits (Info category). Slack is at its 50-slash cap,
  so /credits is routed via /hermes credits on Slack only (new
  _SLACK_VIA_HERMES_ONLY set) to avoid clamping a canonical command off the
  native list and breaking Telegram parity; native everywhere else.
- account_usage.py: build_credits_view() — one portal fetch → balance lines +
  identity line + org-pinned top-up URL + depleted flag, consumed by all
  surfaces. Reuses the same snapshot/URL builder as /usage so numbers match.
- cli.py: _show_credits() — balance block + identity line + 3-button panel
  (Open top-up / Copy link / Cancel) via the existing prompt_toolkit modal.
  ASK, never auto-launch; headless falls back to printing the URL.
- gateway/slash_commands.py: _handle_credits_command() — renders the block +
  tappable top-up URL + no-wait copy; works on button and plain-text platforms.
- /usage credits line now points to /credits.
- Retire `hermes portal topup` (portal_cli.py back to baseline); the engine
  (slug/name parse + nous_portal_topup_url) stays as the shared core.

No polling, no payment confirmation (billing phase 2a). Depends on NAS NousResearch#409.

* fix(credits): /credits works in the TUI slash-worker (non-interactive)

In the TUI, /credits runs in the slash-worker subprocess where there is no
live prompt_toolkit app and stdin is the JSON-RPC pipe. _show_credits called
the 3-button modal unconditionally, which fell back to reading stdin →
exception → slash.exec rejected → the command produced no output (only the
pre-existing 'Credit access paused' banner showed).

- _show_credits: when self._app is None (TUI worker / piped / non-interactive),
  render the text variant — balance block + tappable top-up URL + no-wait line,
  same affordance as the messaging surfaces — and skip the modal entirely. The
  3-button panel still renders in the interactive CLI.
- Depleted banner copy: 'run /usage for balance' → 'run /credits to top up'
  now that /credits is the dedicated money surface (+ tests).
- Regression tests: _show_credits with self._app=None renders text and never
  invokes the modal; logged-out path.

* feat(tui): credits.view RPC for the /credits tappable top-up button

Add a credits.view JSON-RPC method returning the structured CreditsView
(logged_in, balance_lines, identity_line, topup_url, depleted) so the TUI can
render a clickable <Link> top-up button instead of plain text. Account-
independent (portal fetch gated on a logged-in Nous account), fail-open to
{logged_in: false} on any hiccup. Mirrors session.usage's credits-block pattern.

Frontend (TUI-local /credits command + Ink component) lands separately.

* feat(tui): /credits command with keyboard-driven top-up confirm

TUI-local /credits: fetches the structured balance via the credits.view RPC,
prints the balance + identity + top-up URL, then arms the EXISTING confirm
overlay (Enter = open top-up in browser via openExternalUrl, Esc = cancel).
Reuses ConfirmReq — no new overlay component/state/input handler. Headless
(openExternalUrl returns false) falls back to printing the URL.

- gatewayTypes.ts: CreditsViewResponse.
- commands/credits.ts: the command (mirrors /status's rpc+guarded pattern).
- registry.ts: register creditsCommands.
- test: balance+overlay armed, headless fallback, no-url, logged-out (4 cases).

Matches the CLI /credits 'Enter to open' affordance. Phase 2a: no polling.
…CUMENT (NousResearch#44778)

The Teams adapter only handled image/* attachments — documents (the
application/vnd.microsoft.teams.file.download.info consent-free download
payload and any direct-URL non-image attachment) never reached media_urls
at all, so run.py's document-context injection had nothing to surface.
Completes the class-wide sweep from PR NousResearch#44695 (Signal/Email/SimpleX).

- download.info attachments: fetch the pre-authed SharePoint downloadUrl
  (SSRF-guarded, same guard chain as base.py cache_*_from_url) and route
  through cache_media_bytes
- direct-URL non-image attachments: same fetch + classify path
- skip Teams' text/html message-body mirror and adaptive-card attachments
- DOCUMENT > PHOTO > VIDEO > AUDIO precedence for mixed attachments,
  matching the Email precedence rationale from NousResearch#44695
* feat(yuanbao): support wechat forward msg

* feat(yuanbao): support wechat forward msg

---------

Co-authored-by: loongfay <izhaolongfei@gmail.com>
…drafts)

Cherry-pick of upstream PR NousResearch#44780 (ITheEqualizer). Final replies send raw
agent markdown via sendRichMessage; DM streaming previews use
sendRichMessageDraft; MarkdownV2 stays as the fallback path. Opt out with
platforms.telegram.extra.rich_messages: false.

Resolved a trivial conflict with main's supports_code_blocks attribute
(kept both).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… .env (NousResearch#44792)

Two halves of the same community report (dashboard Profile Builder):

1. A fresh dashboard/CLI-created profile got no .env file unless cloned,
   so it silently inherited API keys and messaging tokens from the shell
   environment / root install. create_profile() now seeds a placeholder
   .env (0600) for non-clone profiles, matching the SOUL.md seeding.

2. The Channels endpoints (/api/messaging/platforms GET/PUT/test) were
   not profile-scoped: they read/wrote the dashboard process's own .env
   via load_env()/save_env_value() regardless of the global profile
   switcher. They now accept the standard optional profile param (body
   beats query on the PUT, matching other scoped writes) and run inside
   _profile_scope(). When scoped, the payload no longer falls back to
   os.environ or load_gateway_config()'s env-override layer — both carry
   the ROOT install's credentials and would misreport them as the
   profile's. /api/messaging/platforms added to PROFILE_SCOPED_PREFIXES
   so the sidebar switcher scopes the Channels page automatically.
teknium1 and others added 16 commits June 14, 2026 03:21
…rch#46074)

Gateway startup now queues real inbound messages until restart-interrupted auto-resume turns have completed, preventing duplicate agents for the same session after a restart.
Port 465 expects implicit TLS (SMTP_SSL) from the first byte. The email
adapter always used SMTP() + starttls(), which is correct for port 587
but hangs/fails on port 465 providers (e.g., Swiss ISPs).

Additionally, when the SMTP host has AAAA DNS records but IPv6 is
unreachable, socket.create_connection() tries IPv6 first and hangs
until timeout. Add an IPv4 fallback via AF_INET socket.

Extract _connect_smtp() helper to consolidate the 4 duplicate SMTP
connection sites into a single method with correct protocol selection
and IPv6 fallback logic.
…tinue notes

Three changes to prevent infinite re-execution loops when a user sends
a new message while long-running tools are executing:

1. Filter interrupted tool results in _build_gateway_agent_history:
   skip tool messages whose content contains [Command interrupted] or
   exit_code 130 — they represent partial execution, not valid results.

2. Don't replay auto-continue notes as user messages: detect
   gateway-injected [System note: ...] / [IMPORTANT: ...] prefixes
   and skip them in _build_gateway_agent_history so the LLM doesn't
   see 4+ messages from 'the user' telling it to finish old work.

3. Fix the wording: the system note now instructs the model to
   address the user's NEW message FIRST, IGNORE pending results,
   and NOT re-execute old tool calls.

Closes NousResearch#45230
# Conflicts:
#	apps/desktop/src/app/session/hooks/use-message-stream.ts
@AIalliAI
AIalliAI force-pushed the bugfixes/claude-2026-06-10 branch from 3cf88c2 to dd111ee Compare June 14, 2026 17:51
T02200059 pushed a commit to T02200059/hermes-agent that referenced this pull request Jun 18, 2026
… runs

The genuine-rate-limit branch set retry_count = max_retries before
continue, intending the top-of-loop Nous guard to handle fallback or
bail cleanly. But the loop condition is retry_count < max_retries, so
the guard never ran: no fallback activation, no clean rate-limit
message — just the generic retry-exhaustion error.

Set retry_count = max(0, max_retries - 1) so the loop body runs exactly
once more and the guard sees the breaker state recorded moments earlier.

Extracted from the NousResearch#44061 bugfix rollup by @AIalliAI.
@AIalliAI AIalliAI closed this Jun 18, 2026
xyshanren pushed a commit to xyshanren/hermes-agent-cn that referenced this pull request Jun 25, 2026
… runs

The genuine-rate-limit branch set retry_count = max_retries before
continue, intending the top-of-loop Nous guard to handle fallback or
bail cleanly. But the loop condition is retry_count < max_retries, so
the guard never ran: no fallback activation, no clean rate-limit
message — just the generic retry-exhaustion error.

Set retry_count = max(0, max_retries - 1) so the loop body runs exactly
once more and the guard sees the breaker state recorded moments earlier.

Extracted from the NousResearch#44061 bugfix rollup by @AIalliAI.
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
… runs

The genuine-rate-limit branch set retry_count = max_retries before
continue, intending the top-of-loop Nous guard to handle fallback or
bail cleanly. But the loop condition is retry_count < max_retries, so
the guard never ran: no fallback activation, no clean rate-limit
message — just the generic retry-exhaustion error.

Set retry_count = max(0, max_retries - 1) so the loop body runs exactly
once more and the guard sees the breaker state recorded moments earlier.

Extracted from the NousResearch#44061 bugfix rollup by @AIalliAI.
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
… runs

The genuine-rate-limit branch set retry_count = max_retries before
continue, intending the top-of-loop Nous guard to handle fallback or
bail cleanly. But the loop condition is retry_count < max_retries, so
the guard never ran: no fallback activation, no clean rate-limit
message — just the generic retry-exhaustion error.

Set retry_count = max(0, max_retries - 1) so the loop body runs exactly
once more and the guard sees the breaker state recorded moments earlier.

Extracted from the NousResearch#44061 bugfix rollup by @AIalliAI.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
… runs

The genuine-rate-limit branch set retry_count = max_retries before
continue, intending the top-of-loop Nous guard to handle fallback or
bail cleanly. But the loop condition is retry_count < max_retries, so
the guard never ran: no fallback activation, no clean rate-limit
message — just the generic retry-exhaustion error.

Set retry_count = max(0, max_retries - 1) so the loop body runs exactly
once more and the guard sees the breaker state recorded moments earlier.

Extracted from the NousResearch#44061 bugfix rollup by @AIalliAI.
donbowman pushed a commit to donbowman/hermes-agent that referenced this pull request Jul 13, 2026
… runs

The genuine-rate-limit branch set retry_count = max_retries before
continue, intending the top-of-loop Nous guard to handle fallback or
bail cleanly. But the loop condition is retry_count < max_retries, so
the guard never ran: no fallback activation, no clean rate-limit
message — just the generic retry-exhaustion error.

Set retry_count = max(0, max_retries - 1) so the loop body runs exactly
once more and the guard sees the breaker state recorded moments earlier.

Extracted from the NousResearch#44061 bugfix rollup by @AIalliAI.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
… runs

The genuine-rate-limit branch set retry_count = max_retries before
continue, intending the top-of-loop Nous guard to handle fallback or
bail cleanly. But the loop condition is retry_count < max_retries, so
the guard never ran: no fallback activation, no clean rate-limit
message — just the generic retry-exhaustion error.

Set retry_count = max(0, max_retries - 1) so the loop body runs exactly
once more and the guard sees the breaker state recorded moments earlier.

Extracted from the NousResearch#44061 bugfix rollup by @AIalliAI.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
… runs

The genuine-rate-limit branch set retry_count = max_retries before
continue, intending the top-of-loop Nous guard to handle fallback or
bail cleanly. But the loop condition is retry_count < max_retries, so
the guard never ran: no fallback activation, no clean rate-limit
message — just the generic retry-exhaustion error.

Set retry_count = max(0, max_retries - 1) so the loop body runs exactly
once more and the guard sees the breaker state recorded moments earlier.

Extracted from the NousResearch#44061 bugfix rollup by @AIalliAI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint invalid This doesn't seem right

Projects

None yet

Development

Successfully merging this pull request may close these issues.