Skip to content

st2 ding reports delivery when Codex has not submitted or queued the notice #58

Description

@schickling-assistant

Problem

A successful pty send proves that terminal input was accepted, not that Codex submitted or queued the message. st2 ding currently collapses those two facts into Delivered.

Two outcomes were observed on the same Codex seat minutes apart:

  1. the notice was queued correctly and appeared under Messages to be submitted after next tool call;
  2. the exact notice remained stranded in the composer under tab to queue message and was never acted on until an external key:return was sent.

The second case is a false delivery receipt: the message reached the input line, but not the agent.

Current boundary

The production path builds one terminal command containing bracketed paste, a fixed 0.5 second delay, and key:return:

st2/src/ding/mod.rs

Lines 131 to 142 in 6f1fe48

/// Recovery delivery: one bounded PTY transaction containing paste and Return.
pub fn pty_delivery_args(session: &str, text: &str) -> Vec<String> {
vec![
"send".into(),
session.into(),
"--with-delay".into(),
"0.5".into(),
"--seq".into(),
bracketed_paste(text),
"--seq".into(),
"key:return".into(),
]

PtyPoker::poke_with returns PokeOutcome::Delivered after that command exits successfully; it performs no post-Return observation:

st2/src/ding/mod.rs

Lines 210 to 236 in 6f1fe48

/// Central production safety path shared by inbox DING and any caller that needs to record an
/// attempt immediately before the only command containing Return.
pub fn poke_with(
&self,
text: &str,
before_submit: &mut dyn FnMut() -> anyhow::Result<()>,
) -> anyhow::Result<PokeOutcome> {
// Recovery contract: PTY-alive delivery is transport-first. Keep one owned payload,
// stage it once, wait a bounded interval, then submit exactly once; pane heuristics remain
// diagnostic only and must not silently suppress messaging.
self.run(pty_delivery_args(&self.session, text), "send")
.map_err(|error| anyhow::anyhow!("staging DING payload: {error}"))?;
before_submit()?;
Ok(PokeOutcome::Delivered)
}
}
impl Poker for PtyPoker {
fn poke(&self, text: &str) -> anyhow::Result<PokeOutcome> {
self.poke_with(text, &mut || Ok(()))
}
fn retry_staged(&self, text: &str) -> anyhow::Result<PokeOutcome> {
let _ = text;
self.run(pty_submit_args(&self.session), "send")
.map_err(|error| anyhow::anyhow!("submitting staged DING payload: {error}"))?;
Ok(PokeOutcome::Delivered)

The code already recognizes Messages to be submitted after next tool call as an active/blocked Codex screen shape, but the production transport-first path does not use that observation to establish receipt:

fn interaction_blocked(plain: &str) -> bool {
inherited_progress_line(plain)
|| plain.contains("Working (")
|| plain.contains("esc to interrupt")
|| plain.contains("Esc to interrupt")
|| plain.contains("ctrl+c to interrupt")
|| plain.contains("Messages to be submitted after next tool call")
|| plain.contains("press esc to interrupt and send")
|| plain.contains("Our systems are thinking a bit more")
|| plain.contains("Retry with a faster model")
|| plain.contains("Create a plan?")
|| looks_like_choice_menu(plain)
}

Trigger characterization

This does not reduce to “mid-tool-call versus idle prompt.” Both observed surfaces are active-turn Codex states: the successful case showed the queued-message banner, while the stranded case showed the queue hint beside a populated composer.

The remaining discriminant is a finer Codex input-mode or timing transition that st2 does not observe. That is precisely why PTY process success cannot serve as the delivery receipt.

Expected contract

One of these boundaries should be explicit:

  • Delivered requires a post-Return observation that the exact notice left the composer and became submitted or queued; or
  • the transport result is named something like InputSent, while the notice remains pending until a separate receipt is observed.

If the exact notice remains in the composer, st2 should retain staged ownership and retry submission by inspection, without re-pasting or treating the inbox message as delivered.

Related but distinct

Versions

  • st2 0.1.0+6f1fe48
  • source 6f1fe484b517406b80d06f335997859b1a53f609
  • Codex CLI 0.145.0
  • pty 0.12.0
  • Linux

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:dingDING delivery: inbox notice into a running agent · Set: manualharness:codexCodex-specific behavior · Set: manualorigin:agentFiled or primarily produced by an AI agent · Set: manualtype:bugSomething broken or a regression · Set: manual

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions