Skip to content

fix: flow message silently stored a flag as the body - #95

Merged
rr0hit merged 1 commit into
mainfrom
fix/message-body-parsing
Sep 7, 2026
Merged

rr0hit merged 1 commit into
mainfrom
fix/message-body-parsing

Conversation

@anshulsao

Copy link
Copy Markdown
Member

The bug

flow message took the body as a blind positional (args[1]) with no flag-guard, and Go's flag.Parse stops at the first positional. So any flag written before the body became the literal body, and the real text (and --urgent) were silently dropped:

flow message tgt --urgent "real body"   → body stored = "--urgent"   (text lost, not urgent)
flow message tgt --body   "real body"   → body stored = "--body"     (text lost)
flow message tgt "real body" --urgent   → body = "real body", urgent=1   ✓ (only this exact order worked)

Agents hit this constantly — they write the flag first (natural CLI order) or invent a --body flag — so real messages were being corrupted and delivered as literal --urgent / --body, with no error.

The fix

Rewrite the arg parser to be order-independent and give the body a real name:

  • --urgent recognised anywhere in the args
  • a real --body / -m / --message flag for the text (plus --body=…)
  • -- to end flag parsing (so a body may start with -)
  • unknown flags now error instead of silently becoming the body

All three natural forms now work:

flow message tgt "body" --urgent
flow message tgt --urgent "body"
flow message tgt --body "body"

Tests

  • New TestMessageParsingToleratesFlagOrderAndBodyFlag covers flag-first, --body, positional, and the unknown-flag rejection (nothing stored).
  • Updated TestMessageRejectsFlagAddressAndClosedTasks: a known flag before the address is now valid (address = first positional); the real protection is that unknown flags error. Closed/done-task rejection unchanged.
  • Full suite green.

🤖 Generated with Claude Code

The body was a blind positional (args[1]) with no flag-guard, and Go's
flag.Parse stops at the first positional — so `flow message tgt --urgent
"real body"` stored the body as literal "--urgent" (real text dropped,
urgent lost), and the `--body` agents keep inventing stored "--body".

Rewrite the parser to be order-independent: --urgent anywhere, a real
`--body`/`-m`/`--message` flag, `--` to end flags, and an explicit error
on unknown flags instead of silently corrupting the body. All three
natural forms now work:
  flow message tgt "body" --urgent
  flow message tgt --urgent "body"
  flow message tgt --body "body"
Regression test added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012iRktytszcMqAmiS84SvKL
@anshulsao
anshulsao force-pushed the fix/message-body-parsing branch from dfe0a3d to d7a30e4 Compare September 4, 2026 07:13
@rr0hit
rr0hit merged commit 9d6a93a into main Sep 7, 2026
3 checks passed
rr0hit added a commit that referenced this pull request Sep 7, 2026
Port Anshul's #95 flag-parsing correction into this branch's docs: the
mail-model rewrite of §4.18 had dropped it. references/messaging.md now
states the tolerant command form (body positional or --body; --urgent/
--body/--reply-to may come in any order; unknown flags rejected, never
stored as the body), and the app.go --help note lists --reply-to as a
known flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rr0hit added a commit that referenced this pull request Sep 7, 2026
…, 500-char cap (#96)

* feat: pop-only inbox API; self-send gate; reserved assignee self -> user

- flow inbox pop is the ONLY consumption API: ack and due subcommands
  removed, along with the whole escalation apparatus (next_notify_at /
  attempts columns for fresh installs, DueBusMessages, BumpNotifyAttempt,
  AckMessageByID). Popping answers, delivers, and clears; agents and
  notifier scripts just loop it. Ack-on-reply (the UserPromptSubmit
  hook) and wait metrics remain — that is behavior, not API surface.
- No sender can message its own address: a bound session is rejected
  addressing its own inbox, and the human their own queue.
- Reserved assignee renamed self -> user (agents misread 'message self'
  as talking to themselves). Existing rows migrated idempotently on
  open; the one-shot post->broadcast kind migration is deleted (no
  released DB ever needed it).
- Usage, skill §4.18, and references/messaging.md rewritten to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFGNJQo5xNBBrT7rZMuUTu

* feat(bus): mail-model inbox — --all, read <id>, pop --keep-unread, message --reply-to

Additive, non-breaking additions on top of the pop-only bus:

- `flow inbox --all` lists the whole retained queue (read + unread);
  default `flow inbox` still lists only unread.
- `flow inbox read <id>` shows any message by id (incl. consumed) and
  marks it read — acks a specific message out of pop's oldest-first order.
- `flow inbox pop --wait --keep-unread` is the reader/relay wake: returns
  the oldest unread, claims it `delivered` (loop-safe) but never acks, so
  a forwarder can pass mail along without consuming the human's answer.
- `flow message --reply-to <id>` stamps a parent id (new nullable
  `reply_to` column, migrated on open); the receiver sees the lineage.
- Mail nomenclature (unread/read) in human output and `--json` (`mail`).
- Retention: a delivered-but-unacked human message is immortal like
  pending — an unanswered question never vanishes, whoever forwarded it.

Skill §4.18 + references/messaging.md rewritten: mail nomenclature,
two-purpose framing (reach-out-to-user / peer-collab), two Monitor
recipes (consumer pop-wait, reader pop-wait --keep-unread).

Vanishing-message bug ruled out as durability: the ids were auto-acked
by the sending session's UserPromptSubmit hook (acked_by='prompt') before
the relay forwarded them, not lost from storage. `--keep-unread` closes
the delivery-starvation gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(bus): raise body cap to 500; auto-mode progress guidance

- Bump busBodyMax 200 -> 500 (non-breaking; short messages unaffected).
  The 200 cap forced constant splitting for the Telegram relay.
- Skill (SKILL.md §4.18 + references/messaging.md): document that
  headless `flow do --auto` agents should post regular progress via
  `flow message user`/`flow broadcast`, not only when blocked — a silent
  run looks stuck. Update the two "≤200 chars" citations to "≤500".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(bus): delta-gated inbox nudge at PreToolUse

Surface a just-arrived DIRECTED inbox message right before a tool runs, so
a mid-turn "stop, don't do X" reaches the agent before the action it would
cancel — the case SessionStart/UserPromptSubmit/Stop nudges surface too
late.

The existing pending-count notice is NOT delta-gated (raw COUNT(*) of
pending rows), so wiring it as-is into PreToolUse — which fires on every
tool call — would re-nudge the same set every call. Instead add a
bus_surfaced high-water mark: PendingDirectedUnsurfacedForTask returns only
messages not yet announced, MarkSurfaced records them, so each message is
surfaced at most once and quiet tool calls stay silent. Broadcasts are
excluded (FYIs, not action-changing); urgent messages lead. Inform-only —
hooks never consume.

Wires `flow hook pre-tool-use` into settings.json via a new
Install/UninstallPreToolUseHook on the harness interface (claude installs;
codex no-ops), and on skill install/uninstall/auto-upgrade. bus_surfaced
marks are cleaned on task close-out and pruned in SweepBus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(bus): PreToolUse nudge surfaces broadcasts too (any unread)

The PreToolUse delta-gated nudge previously surfaced only directed
messages, excluding broadcasts as FYIs. Per refinement, it now fires on
ANY unread item (message OR broadcast) addressed to the session: rename
PendingDirectedUnsurfacedForTask -> PendingUnsurfacedForTask and drop the
kind='message' filter. Delta-gating (bus_surfaced high-water mark), urgent
lead, and inform-only (never consumes) are unchanged, so each new unread
still surfaces exactly once with no per-call spam. Test flipped from
"ignores broadcasts" to "surfaces broadcasts" (once, then delta-gated);
wording + docs (messaging.md) updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(bus): align skill + help with #95's corrected message command

Port Anshul's #95 flag-parsing correction into this branch's docs: the
mail-model rewrite of §4.18 had dropped it. references/messaging.md now
states the tolerant command form (body positional or --body; --urgent/
--body/--reply-to may come in any order; unknown flags rejected, never
stored as the body), and the app.go --help note lists --reply-to as a
known flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.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.

2 participants