Skip to content

Bash↔PowerShell drift: airc.ps1 is ~20 commits behind canary #152

@joelteply

Description

@joelteply

State

`airc` (bash) is at canary HEAD with all the recent IRC verb work, sticky /part, cross-scope whois, etc.

`airc.ps1` was last touched at #114 (3f812af, "fix(tailscale): just run `tailscale up` synchronously"). Since then, the bash port has shipped:

PR What Mirrored in airc.ps1?
#115 cold-host split-brain race-loser detection NO
#116 three resume bugs NO
#117 dead-monitor send check NO
#118 stale-gist 404 self-heal NO
#120 resume success banner NO
#122 default-on #general sidecar (multi-room) NO
#123 --room flag (cross-room send) NO
#124 peers cross-scope aggregation NO
#125 part keeps sidecar NO
#129 platform adapters extraction NO
#131 always-discover (delete resume code) NO
#135 whois cross-scope NO
#138 sticky /part with parted_rooms NO
#139 airc away/back + IRC verb coverage NO
#150 _primary_scope_for empty-prefix guard NO

A Windows user on the current main (post-#144 promotion) gets a substantively different airc than a Mac user. Same surface area but ~half the implementation.

Why this matters

Joel's CLAUDE.md (`Native-truth, thin-SDK-per-language architecture`):

The performant native layer is the truth. Higher-level languages get thin SDKs that wrap it idiomatically — they don't reimplement, they don't carry their own version of the truth, and they don't shortcut around the native layer "for ease."

airc currently violates this. There's no native truth — there are TWO partial truths in different shells, and they drift.

Joel 2026-04-27: "look for ways to keep these consistent, permanently."

Proposed direction

Python truth-layer. Both `airc` (bash) and `airc.ps1` (powershell) become thin dispatchers that exec a Python core for business logic. Python is already a hard prereq (the bash script invokes `python3` dozens of times in heredocs), so adoption cost is zero — we just promote the embedded heredocs to proper modules.

What stays in shell:

  • Process management (spawn, kill, signal handling)
  • Port binding (only because Python's socket module cleanup-on-exit is fussy under SIGTERM)
  • SSH command construction (shell-out is the natural call shape)
  • arg parsing (each shell has its own idioms; thin)

What moves to Python:

What stays per-shell:

  • `airc.ps1` arg parsing + dispatch
  • `airc` arg parsing + dispatch
  • Platform adapters that are GENUINELY platform-specific (Windows Task Scheduler vs systemd vs launchd — these can't be Python-unified because the OS-level service registration is fundamentally different)

Migration strategy

Don't rewrite. Adopt incrementally:

  1. Pick ONE feature with high duplication risk (e.g. parted_rooms — read/write/clear).
  2. Implement in `lib/state.py` as a CLI: `python3 -m lib.state record-parted general`.
  3. Have `airc` shell out to it; have `airc.ps1` shell out to the same thing.
  4. Verify both work; ship.
  5. Repeat for the next feature.

Each migration ships with cross-port tests (drive both `airc` and `airc.ps1` through the same scenario, compare output) so drift becomes mechanical to catch.

Risk

  • Python startup overhead (~50-100ms cold start) per shell-out. Acceptable for non-hot-path commands; might be noticeable for frequent ones (cmd_send, monitor formatter). Mitigation: keep hot-path logic in shell where the latency matters, move only the cold-path business logic.

Out of scope

This is the architectural piece. Concrete migrations land as separate issues per feature.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions