Skip to content

fix: keep courier iMessage pickup running across reboots - #18

Merged
Mauryanx merged 3 commits into
mainfrom
fm/fm-pickup-runtime
Oct 9, 2026
Merged

Mauryanx merged 3 commits into
mainfrom
fm/fm-pickup-runtime

Conversation

@Mauryanx

@Mauryanx Mauryanx commented Oct 9, 2026

Copy link
Copy Markdown
Owner

Intent

Part of the overnight hardening build: the smallest durable best-practice change, the way Kun builds Firstmate, never a today-only patch and never over-engineered. VM re-test finding P1: nothing starts Firstmate's courier iMessage pickup (bin/fm-courier-pickup.py run, merged in #17). On the VM it ran as a transient systemd-run --user unit and had to be restarted by hand after every reboot. Ship how the pickup runs so it survives reboots, before courier activation.

Referenced substance of #17: bin/fm-courier-pickup.py is Firstmate's side of the courier iMessage wire. Running as Firstmate's own account with an explicit FM_HOME and the FM_NOTIFY_COURIER=1 opt-in, its run mode continuously reads the courier's inbound spool (/srv/courier/inbound) read-only, files each captain message into Firstmate's conversation transport, and answers through Firstmate-owned courier outbox requests; it is off unless FM_NOTIFY_COURIER=1, and nothing launched it.

What Changed

  • Add a systemd user-unit renderer and installer for courier iMessage pickup, with explicit home and opt-in settings, restart on failure, and boot startup when enabled with account lingering.
  • Document activation, updates, and rollback while keeping service enablement separate from installation.
  • Extend courier regression coverage for unit settings, startup commands, and path validation, and route service-script changes to those checks.

Risk Assessment

✅ Low: The change is bounded, preserves existing account and transport authorization, and documents reboot and activation prerequisites.

Testing

The targeted courier suite and disposable CLI checks passed for installation, effective settings, enablement and rollback, path refusals, pickup execution, recovery, and test selection. CLI transcripts and persisted artifacts were retained; isolated systemd startup was blocked by cgroup permissions, leaving crash restart and reboot checks untested. Temporary fixtures and downloaded packages were removed.

  • Live validation: ✅ go - 6 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Install a valid unit for the selected home without enabling or starting courier pickup ✅ pass live courier-launcher-manual.log:1–26; courier-pickup-tests.log:44 — installed unit validated by systemd-analyze and effective-setting assertions.
Enable and disable the installed unit using systemctl against isolated configuration ✅ pass live courier-launcher-manual.log:27–36 — real systemctl creates the default.target link, reports enabled, and removes the link on disable.
Reinstall for another home and reject unsafe paths while preserving the existing unit ✅ pass live courier-launcher-manual.log:37–55 — home replacement succeeds; relative, missing, whitespace-containing, and percent-containing paths are refused without replacing the installed unit.
Run the emitted pickup command and recover its cursor after termination without refiling the original message ✅ pass live courier-launcher-manual.log:56–63; pickup-recovered-state.json — emitted command files two distinct turns across explicit relaunch, advances cursor to 2, and preserves original spool bytes.
Run pickup without FM_NOTIFY_COURIER=1 and observe refusal without I/O ✅ pass live tests/fm-courier-pickup.test.sh; courier-pickup-tests.log:2 — real pickup subprocess exits 3 and creates no pickup state without opt-in.
Change only the launcher and have changed-file selection include the courier pickup suite ✅ pass live launcher-selection.log — real runner selects tests/fm-courier-pickup.test.sh when only the launcher changes in an isolated git fixture.
Crash the service and observe systemd automatically restart pickup after five seconds ⏸️ untested no Downloaded and extracted systemd inside the worktree and attempted an isolated user manager through bwrap. Startup failed with EACCES opening /sys/fs/cgroup/init.scope/cgroup.procs. The operator manag…
Reboot a host with the lingering account and enabled unit and observe pickup start without login ⏸️ untested no Verified persistent enablement with real systemctl, then attempted a workspace-local systemd manager in an isolated namespace; cgroup permission denied prevented startup. Rebooting the operator host o…
Evidence: Launcher installation, enablement, refusals, and pickup recovery

Source: Launcher installation, enablement, refusals, and pickup recovery

$ ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup-service.sh install
~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/fm-courier-pickup.service
exit=0
Installed mode=0644; no service-control calls, no target enablement, no pickup state.
# Generated by ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup-service.sh; re-run its install rather than editing.
[Unit]
Description=Firstmate courier iMessage pickup for ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/home
Documentation=file://~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/docs/voice-relay.md

[Service]
Type=simple
Environment=FM_HOME=~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/home
Environment=FM_NOTIFY_COURIER=1
WorkingDirectory=~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/home
ExecStart=~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup.py run
Restart=on-failure
RestartSec=5
NoNewPrivileges=yes

[Install]
WantedBy=default.target

$ systemd-analyze verify ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/fm-courier-pickup.service
/usr/lib/systemd/system/xfs_scrub_all.service:26: Support for option CPUAccounting= has been removed and it is ignored
/usr/lib/systemd/system/system-xfs_scrub.slice:15: Support for option CPUAccounting= has been removed and it is ignored
exit=0
$ /usr/bin/systemctl --user --no-reload enable ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/fm-courier-pickup.service
Created symlink '~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/default.target.wants/fm-courier-pickup.service' → '~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/fm-courier-pickup.service'.
exit=0
Persistent default.target link: ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/default.target.wants/fm-courier-pickup.service -> ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/fm-courier-pickup.service
$ /usr/bin/systemctl --user --no-reload is-enabled fm-courier-pickup.service
enabled
exit=0
$ /usr/bin/systemctl --user --no-reload disable fm-courier-pickup.service
Removed '~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/default.target.wants/fm-courier-pickup.service'.
exit=0
$ ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup-service.sh install
~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/fm-courier-pickup.service
exit=0
$ ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup-service.sh install
fm-courier-pickup-service.sh: FM_HOME must be an absolute path: relative/home
exit=1
$ ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup-service.sh install
fm-courier-pickup-service.sh: FM_HOME has characters this unit does not escape: ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/has space
exit=1
$ ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup-service.sh install
fm-courier-pickup-service.sh: FM_HOME is not a directory: ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/missing
exit=1
$ ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup-service.sh install
fm-courier-pickup-service.sh: FM_HOME has characters this unit does not escape: ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/bad%specifier
exit=1
$ ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup-service.sh install
~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/config/systemd/user/fm-courier-pickup.service
exit=0
Reinstall selected the new home; invalid paths preserved the installed unit; no temporary unit files remain.
Starting effective ExecStart with emitted Environment and WorkingDirectory: ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup.py run
Running pickup created one conversation note and queued wake from message manual1.
Killed pickup; explicitly relaunching its command to check cursor recovery (not systemd restart).
After explicit relaunch: turn IDs=["imsg-manual1", "imsg-manual2"]; cursor=2; original spool bytes unchanged.
Queued wake records:
1791536132	1	check	inbox:vc-c0714a135f8be4dec63ee31e844b57c0dd661e1242f9b2f25a28433fc561873f	check: conversation inbox note vc-c0714a135f8be4dec63ee31e844b57c0dd661e1242f9b2f25a28433fc561873f; use fm-inbox.sh conversation accept
1791536133	2	check	inbox:vc-c0714a135f8be4dec63ee31e844b57c0dd661e1242f9b2f25a28433fc561873f	check: conversation inbox note vc-c0714a135f8be4dec63ee31e844b57c0dd661e1242f9b2f25a28433fc561873f; use fm-inbox.sh conversation accept
1791536133	3	check	inbox:vc-8be3ffd962bc8cd07047387ec1d10806941c161e926dd07a6281c3d47b1ddc42	check: conversation inbox note vc-8be3ffd962bc8cd07047387ec1d10806941c161e926dd07a6281c3d47b1ddc42; use fm-inbox.sh conversation accept

All disposable service and pickup checks completed.
Evidence: Targeted courier suite transcript

Source: Targeted courier suite transcript

FM_TEST_BEGIN 2026-10-09T08:52:32Z tests/fm-courier-pickup.test.sh family=standalone expected_gate_skip=none
PASS: without FM_NOTIFY_COURIER=1 the pickup exits 3 and touches nothing
PASS: a spool record becomes exactly one turn and one wake, acknowledged filed; a rescan files nothing again
PASS: an operational message-read error preserves the cursor and files the same record after restart
PASS: duplicate, wrong-mode, mismatched, extra-field, duplicate-key, NaN, symlinked and hard-linked records are refused
PASS: a record that vanishes between listing and open is refused and passed
PASS: accepted turns show working; a numbered question is texted with its poll; its stage follows the sent receipt
PASS: a vote on a question it asked is filed once with that question's binding; other votes are not filed
PASS: an operational vote-read error preserves ordering and captures the original choice after restart
PASS: a question delayed two days keeps its watch across restart and accepts a vote after delivery
PASS: an empty vote leaves the question watch available for a later nonempty vote across restart
PASS: direct and propagated progress preserve question rank; a late original reply preserves its follower link
PASS: a final answer marks done; stages are published in order and never backwards
PASS: an unrelated text preserves the established vote follower and its original done reaction
PASS: a vote answer with together receipt preserves the original done reaction and clears its answer flag
PASS: a text answer with together receipt preserves the original done reaction and clears its answer flag
PASS: a vote answer with delayed receipt preserves the original done reaction and clears its answer flag
PASS: a text answer with delayed receipt preserves the original done reaction and clears its answer flag
PASS: a text answer with refused receipt preserves the original done reaction and clears its answer flag
PASS: a queued second question preserves the first question's text answer binding and done reaction
PASS: a refused second question preserves the first question's vote answer binding and done reaction
PASS: a question without poll options awaits an answer at claim and clears the flag on failure
PASS: a pruned receipt is recovered by byte-identical same-ID republication, limited to once per 60 s
PASS: an unknown poll receipt holds the queue, playback, reaction and watch until its sent receipt
PASS: unsupported failed and refused receipt results do not settle a question or release later replies
PASS: an unpublished numbered question ignores unrelated text and finishes only after its later answer
PASS: a numbered question whose outbox publication fails stays ineligible until publication succeeds
PASS: an unpublished unnumbered question ignores unrelated text and finishes only after its later answer
PASS: a unnumbered question whose outbox publication fails stays ineligible until publication succeeds
PASS: a consumed text with an unknown result holds later portions past 300 s until a terminal receipt
PASS: a refused poll has no vote watch; a queued failure reaction waits for its own notice receipt
PASS: a refused question retires answer eligibility and does not follow a later unrelated request
PASS: a delivered poll expires after its delivery-based watch lifetime
PASS: an empty message is told NOT_TEXT and marked failed; an unsaved attachment is filed with its notice
PASS: a message that cannot be filed is retried, then told the failure sentence and marked failed
PASS: a 16001-character transcript within the wire byte limit attempts capture and receives the normal failure notice and stage
PASS: a vote that exhausts capture retries sends the failure notice without a synthetic-message reaction
pickup latency (rename to queued wake), seconds: 0.790 0.578 0.558 0.577 0.538
PASS: each record reaches the wake queue in under a second while the pickup runs
slow-failure stage after 85.7 s
PASS: a hung capture sends one failure notice before its failed stage, all within 120 s of pickup
PASS: the spool is left exactly as the courier left it after deleting its vanished record
ok - courier pickup: one turn per record, refusals, replies, polls, stages and latency
ok - courier pickup service: valid unit runs this pickup for its home, refuses unsafe paths
FM_TEST_END 2026-10-09T08:56:57Z tests/fm-courier-pickup.test.sh exit=0 duration_ms=265097 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=265303
FM_TEST_SUMMARY_FAMILY family=standalone count=1 duration_ms=265097 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-courier-pickup.test.sh duration_ms=265097
Evidence: Installed systemd unit

Source: Installed systemd unit

# Generated by ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup-service.sh; re-run its install rather than editing.
[Unit]
Description=Firstmate courier iMessage pickup for ~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/home
Documentation=file://~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/docs/voice-relay.md

[Service]
Type=simple
Environment=FM_HOME=~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/home
Environment=FM_NOTIFY_COURIER=1
WorkingDirectory=~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/.test-phase-tmp/manual/home
ExecStart=~/.no-mistakes/worktrees/8352e211db7e/01M4FWK9GMKEGGQ4PYBSNRW5CQ/bin/fm-courier-pickup.py run
Restart=on-failure
RestartSec=5
NoNewPrivileges=yes

[Install]
WantedBy=default.target
Evidence: Pickup state after explicit relaunch

Source: Pickup state after explicit relaunch

{
  "cursor": 2,
  "handled": {
    "message-manual1": 1791507600,
    "message-manual2": 1791507600
  },
  "marks": {
    "manual1": {
      "moved": 1791536133.4313045,
      "request": "imsg-req-manual1",
      "shown": "filed",
      "stage": "filed"
    },
    "manual2": {
      "moved": 1791536133.9633055,
      "request": "imsg-req-manual2",
      "shown": "filed",
      "stage": "filed"
    }
  },
  "pending": null,
  "polls": {},
  "stages": 2,
  "texts": [],
  "version": 1
}
Evidence: Launcher-only test selection

Source: Launcher-only test selection

Only changed product path: bin/fm-courier-pickup-service.sh
$ bin/fm-test-run.sh --list --changed --base aff6f9bbacc4d74e76325b1cc5da1c24f924883d
tests/fm-courier-pickup.test.sh
The real runner selected the pickup suite for the launcher-only change.
Evidence: Isolated systemd manager permission blocker

Source: Isolated systemd manager permission blocker


WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

Get:1 https://mirror.hetzner.com/ubuntu/packages resolute-updates/main amd64 systemd amd64 259.5-0ubuntu3.4 [3129 kB]
Fetched 3129 kB in 0s (8601 kB/s)
isolated manager exit=1

Workspace-local systemd package extracted without installing or changing host packages.
Attempt: bwrap --unshare-user --unshare-pid --unshare-cgroup --ro-bind / / --bind <worktree> <worktree> --proc /proc --die-with-parent <workspace-systemd> --user --unit=default.target
HOME, XDG_CONFIG_HOME, XDG_RUNTIME_DIR and SYSTEMD_UNIT_PATH isolated under this worktree.
Systemd startup syscall evidence (strace):
2802973 openat(AT_FDCWD, "/sys/fs/cgroup/init.scope/cgroup.procs", O_WRONLY|O_NOCTTY|O_CLOEXEC) = -1 EACCES (Permission denied)
2802973 exit_group(1)                   = ?
An isolated manager cannot join its cgroup with this process authority; host cgroup mounts were kept read-only to protect operator state. A disposable systemd VM or an independently delegated writable cgroup is required for supervised restart and boot checks.
Evidence: Manual CLI validation driver

Source: Manual CLI validation driver

import configparser
import json
import os
from pathlib import Path
import pwd
import shutil
import stat
import subprocess
import sys
import time

root = Path.cwd()
base = root / '.test-phase-tmp' / 'manual'
evidence = Path('~/.no-mistakes/evidence/01M4FWK9GMKEGGQ4PYBSNRW5CQ')
home = base / 'home'
config = base / 'config'
for directory in (home, config, base / 'runtime', base / 'bin'):
    directory.mkdir(parents=True, exist_ok=True)
(base / 'runtime').chmod(0o700)
env = {key: value for key, value in os.environ.items() if not key.startswith('FM_')}
env.update(HOME=str(base), FM_HOME=str(home), XDG_CONFIG_HOME=str(config),
           XDG_CONFIG_DIRS=str(base / 'config-dirs'), XDG_DATA_HOME=str(base / 'data'),
           XDG_DATA_DIRS=str(base / 'data-dirs'), XDG_RUNTIME_DIR=str(base / 'runtime'),
           DBUS_SESSION_BUS_ADDRESS='unix:abstract=no-mistakes-private-no-bus', SYSTEMD_OFFLINE='1')
service = root / 'bin/fm-courier-pickup-service.sh'
unit = config / 'systemd/user/fm-courier-pickup.service'
systemctl = shutil.which('systemctl')

def run(command, use_env=None, code=0):
    result = subprocess.run([str(item) for item in command], env=use_env or env,
                            capture_output=True, text=True, timeout=30)
    print('$ ' + ' '.join(str(item) for item in command), flush=True)
    print(result.stdout + result.stderr, end='', flush=True)
    print('exit=' + str(result.returncode), flush=True)
    assert result.returncode == code, result
    return result

# Audit only the service-control boundary; execute the real installer.
shim = base / 'bin/systemctl'
marker = base / 'systemctl-called'
shim.write_text('#!/bin/sh\nprintf "%s\\n" "$*" >> "' + str(marker) + '"\nexit 99\n')
shim.chmod(0o755)
install_env = dict(env, PATH=str(shim.parent) + ':' + env['PATH'])
run([service, 'install'], install_env)
assert not marker.exists(), 'install attempted service activation'
assert stat.S_IMODE(unit.stat().st_mode) == 0o644
assert not (home / 'state/courier-pickup').exists()
assert not list(config.rglob('*.wants'))
print('Installed mode=0644; no service-control calls, no target enablement, no pickup state.', flush=True)
print(unit.read_text(), flush=True)
(evidence / 'installed-courier-pickup.service').write_bytes(unit.read_bytes())
run(['systemd-analyze', 'verify', unit])

# Use the real consumer offline: the missing private bus prevents touching the
# operator manager, and --no-reload makes enable/disable file-only operations.
run([systemctl, '--user', '--no-reload', 'enable', unit])
link = config / 'systemd/user/default.target.wants/fm-courier-pickup.service'
assert link.is_symlink() and link.resolve() == unit
print('Persistent default.target link: ' + str(link) + ' -> ' + os.readlink(link), flush=True)
run([systemctl, '--user', '--no-reload', 'is-enabled', unit.name])
run([systemctl, '--user', '--no-reload', 'disable', unit.name])
assert not link.exists()

# Replacement is deterministic, supports a changed home, and refuses bad homes
# before touching a previously installed unit.
new_home = base / 'new-home'
new_home.mkdir()
run([service, 'install'], dict(install_env, FM_HOME=str(new_home)))
updated = unit.read_bytes()
parser = configparser.ConfigParser(interpolation=None)
# Environment repeats; the other scalar values are sufficient for this check.
parser.read_string('\n'.join(line for line in updated.decode().splitlines() if not line.startswith('Environment=')))
assert parser['Service']['WorkingDirectory'] == str(new_home)
for bad in ('relative/home', str(base / 'has space'), str(base / 'missing'), str(base / 'bad%specifier')):
    if 'has space' in bad or '%specifier' in bad:
        Path(bad).mkdir(exist_ok=True)
    run([service, 'install'], dict(install_env, FM_HOME=bad), code=1)
    assert unit.read_bytes() == updated
assert not list(unit.parent.glob('.fm-courier-pickup.service.*'))
run([service, 'install'], install_env)
print('Reinstall selected the new home; invalid paths preserved the installed unit; no temporary unit files remain.', flush=True)

# Drive the effective installed command continuously through real inbox and
# pickup entry points, as the fixture shell keeps the public session lock alive.
settings = {}
for line in unit.read_text().splitlines():
    if line.startswith('Environment='):
        key, value = line[len('Environment='):].split('=', 1)
        settings[key] = value
    elif line.startswith('ExecStart='):
        command = line[len('ExecStart='):].split()
    elif line.startswith('WorkingDirectory='):
        cwd = line[len('WorkingDirectory='):]
courier = base / 'courier'
settings.update(FM_COURIER_ROOT=str(courier), FM_COURIER_USER=pwd.getpwuid(os.getuid()).pw_name)
pickup_env = dict(env, **settings)
(home / 'state').mkdir(exist_ok=True)
(home / 'state/.lock').write_text(sys.argv[1] + '\n')
cli = root / 'bin/fm-inbox.sh'

def conversation(action, payload):
    result = subprocess.run([str(cli), 'conversation', action], input=json.dumps(payload),
                            env=pickup_env, text=True, capture_output=True, timeout=30)
    assert result.returncode == 0, result
    return json.loads(result.stdout)

conversation('pilot-init', {'publication_policy': 'owner-authored-v2', 'destinations': ['imessage']})
binding = conversation('bind', {'conversation_id': 'text', 'authenticated_principal': 'captain', 'destination': 'imessage'})
(home / 'state/imessage').mkdir(mode=0o700)
(home / 'state/imessage/binding.json').write_text(json.dumps(dict(binding, destination='imessage')))
for directory in ('srv/courier/inbound', 'srv/courier/outbox', 'srv/courier/inbox', 'etc/courier'):
    (courier / directory).mkdir(parents=True)
(courier / 'etc/courier/policy.toml').write_text('version = 1\ndefault = "deny"\nteam = []\n[captain]\nname = "Captain"\nto = "+12025550101"\nchannels = ["imessage"]\n')

def publish(seq):
    body = dict(kind='courier-inbound', version=1, seq=seq, type='message',
                published_at='2026-10-09T01:00:00Z', message_id='manual' + str(seq),
                chat_id='chat-1', created_at='2026-10-09T01:00:00Z',
                transcript='Message ' + str(seq) + ' from disposable courier spool', attachments=[], other_parts=0)
    path = courier / ('srv/courier/inbound/%012d-message-manual%d.json' % (seq, seq))
    path.write_text(json.dumps(body))
    path.chmod(0o640)
    return path.read_bytes()

def wait_notes(count, process):
    deadline = time.monotonic() + 10
    while len(list((home / 'state/inbox').glob('vc-*.note'))) != count:
        assert process.poll() is None, 'pickup exited'
        assert time.monotonic() < deadline, 'pickup did not file the spool record'
        time.sleep(0.05)

log = (evidence / 'effective-pickup-stderr.log').open('w')
process = None
try:
    print('Starting effective ExecStart with emitted Environment and WorkingDirectory: ' + ' '.join(command), flush=True)
    process = subprocess.Popen(command, env=pickup_env, cwd=cwd, stdout=log, stderr=log)
    first = publish(1)
    wait_notes(1, process)
    print('Running pickup created one conversation note and queued wake from message manual1.', flush=True)
    process.kill()
    process.wait(timeout=10)
    print('Killed pickup; explicitly relaunching its command to check cursor recovery (not systemd restart).', flush=True)
    process = subprocess.Popen(command, env=pickup_env, cwd=cwd, stdout=log, stderr=log)
    publish(2)
    wait_notes(2, process)
    time.sleep(0.3)
    requests = conversation('audit', {'conversation_id': 'text'})['requests']
    assert [row['turn_id'] for row in requests] == ['imsg-manual1', 'imsg-manual2'], requests
    assert (courier / 'srv/courier/inbound/000000000001-message-manual1.json').read_bytes() == first
    state = json.loads((home / 'state/courier-pickup/state.json').read_text())
    assert state['cursor'] == 2
    print('After explicit relaunch: turn IDs=' + json.dumps([row['turn_id'] for row in requests]) + '; cursor=2; original spool bytes unchanged.', flush=True)
    wakes = (home / 'state/.wake-queue').read_text()
    print('Queued wake records:\n' + wakes, flush=True)
    (evidence / 'pickup-recovered-state.json').write_text(json.dumps(state, indent=2))
finally:
    if process is not None and process.poll() is None:
        process.terminate()
        process.wait(timeout=10)
    log.close()
print('All disposable service and pickup checks completed.', flush=True)

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • ⚠️ tests/fm-courier-pickup.test.sh:34 - The generated configuration checks use raw string matching instead of asserting effective systemd semantics, contrary to the test-quality rule. For example, appending Restart=no after Restart=on-failure would still pass the presence check and syntax verification while disabling crash recovery. Replace the checks at tests/fm-courier-pickup.test.sh:34 (required directives) and tests/fm-courier-pickup.test.sh:36 (forbidden directives) with normalized, section-aware assertions or real-consumer property checks. Cover ExecStart, both Environment assignments, WorkingDirectory, Restart, NoNewPrivileges, WantedBy, User, Group, AmbientCapabilities, CapabilityBoundingSet and ReadWritePaths. Use the documented launcher contract as the oracle and account for repeated assignments and equivalent values described by systemd syntax. The direct startup check does not exercise restart behavior.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Install a valid unit for the selected home without enabling or starting courier pickup ✅ pass live courier-launcher-manual.log:1–26; courier-pickup-tests.log:44 — installed unit validated by systemd-analyze and effective-setting assertions.
Enable and disable the installed unit using systemctl against isolated configuration ✅ pass live courier-launcher-manual.log:27–36 — real systemctl creates the default.target link, reports enabled, and removes the link on disable.
Reinstall for another home and reject unsafe paths while preserving the existing unit ✅ pass live courier-launcher-manual.log:37–55 — home replacement succeeds; relative, missing, whitespace-containing, and percent-containing paths are refused without replacing the installed unit.
Run the emitted pickup command and recover its cursor after termination without refiling the original message ✅ pass live courier-launcher-manual.log:56–63; pickup-recovered-state.json — emitted command files two distinct turns across explicit relaunch, advances cursor to 2, and preserves original spool bytes.
Run pickup without FM_NOTIFY_COURIER=1 and observe refusal without I/O ✅ pass live tests/fm-courier-pickup.test.sh; courier-pickup-tests.log:2 — real pickup subprocess exits 3 and creates no pickup state without opt-in.
Change only the launcher and have changed-file selection include the courier pickup suite ✅ pass live launcher-selection.log — real runner selects tests/fm-courier-pickup.test.sh when only the launcher changes in an isolated git fixture.
Crash the service and observe systemd automatically restart pickup after five seconds ⏸️ untested no Downloaded and extracted systemd inside the worktree and attempted an isolated user manager through bwrap. Startup failed with EACCES opening /sys/fs/cgroup/init.scope/cgroup.procs. The operator manag…
Reboot a host with the lingering account and enabled unit and observe pickup start without login ⏸️ untested no Verified persistent enablement with real systemctl, then attempted a workspace-local systemd manager in an isolated namespace; cgroup permission denied prevented startup. Rebooting the operator host o…
  • TMPDIR="$PWD/.test-phase-tmp" bin/fm-test-run.sh tests/fm-courier-pickup.test.sh
  • .test-phase-tmp/codex -c 'PYTHONDONTWRITEBYTECODE=1 python3 .test-phase-tmp/manual.py "$$"; exit "$?"'
  • systemd-analyze verify &lt;installed-unit&gt; against the generated disposable unit
  • SYSTEMD_OFFLINE=1 systemctl --user --no-reload enable &lt;installed-unit&gt;, is-enabled fm-courier-pickup.service, and disable fm-courier-pickup.service with isolated XDG configuration
  • bin/fm-test-run.sh --list --changed --base &lt;fixture-base&gt; in a launcher-only git fixture
  • unshare --user --map-root-user --pid --fork true
  • apt download systemd and dpkg-deb --extract inside the disposable worktree
  • Workspace-local systemd --user --unit=default.target under bwrap, diagnosed with strace
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Nothing started bin/fm-courier-pickup.py: on the VM it ran as a transient
systemd-run --user unit and had to be restarted by hand after every reboot.

bin/fm-courier-pickup-service.sh renders fm-courier-pickup.service from this
checkout's resolved paths and an explicit FM_HOME, and install writes it
atomically into the account's systemd user directory. The unit runs
`fm-courier-pickup.py run` with the FM_NOTIFY_COURIER=1 opt-in, is wanted by
default.target (boot start for a lingering account), restarts 5 s after any
failure, and runs as the installing account with NoNewPrivileges and no
identity, capability or path grant of its own. Install never runs systemctl,
because enabling the unit is the courier activation step.

docs/voice-relay.md gains the install, enable, update and rollback runbook.
The pickup regression now installs the unit into a temporary config
directory, checks it with systemd-analyze verify where available, and runs the
unit's own ExecStart with its own environment to prove it reaches this
checkout's pickup for the named home; unsafe FM_HOME paths are refused.

Boot start under linger and restart-on-failure under a live user manager are
deferred to the VM pass and unverified here.
@Mauryanx
Mauryanx merged commit 4b73a91 into main Oct 9, 2026
35 of 37 checks passed
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.

1 participant