Skip to content

[Detail Bug] Tail command writes extra newline for command records in text formatΒ #125

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_89d327b3-b883-4365-b6a3-46b6701342a9/bugs/bug_f57c7f81-b467-4fd4-916e-e868c178feb2

Summary

  • Context: The Tail command is used to follow a stream and output records, similar to the Unix tail -f command, and it handles both data records and command records (fence/trim operations).
  • Bug: The Tail command always writes a newline after every record to stdout, including command records in text format, even though command records should not output anything to stdout.
  • Actual vs. expected: Command records in text format are written to stderr only and should not produce stdout output, but Tail writes an extra blank line to stdout for each command record, while the Read command correctly skips this newline.
  • Impact: When using tail -f with text format on streams containing command records (fence/trim operations), blank lines are inserted into the stdout output, corrupting the output stream and potentially breaking pipelines or downstream consumers.

Code with bug

From src/main.rs:

loop {
    select! {
        record = records.next() => {
            match record {
                Some(Ok(record)) => {
                    write_record(&record, &mut writer, args.format).await?;
                    writer
                        .write_all(b"\n")  // <-- BUG πŸ”΄ Always writes newline, even for command records
                        .await
                        .map_err(|e| CliError::RecordWrite(e.to_string()))?;
                    writer
                        .flush()
                        .await
                        .map_err(|e| CliError::RecordWrite(e.to_string()))?;

Codebase inconsistency

Reference (Read command in src/main.rs):

for record in &batch.records {
    write_record(record, &mut writer, args.format).await?;
    let skip_newline = matches!(args.format, RecordFormat::Text)
        && record.is_command_record();
    if !skip_newline {
        writer
            .write_all(b"\n")
            .await
            .map_err(|e| CliError::RecordWrite(e.to_string()))?;
    }
}

Current (Tail command in src/main.rs):

write_record(&record, &mut writer, args.format).await?;
writer
    .write_all(b"\n")  // Always writes newline, no check
    .await
    .map_err(|e| CliError::RecordWrite(e.to_string()))?;

Contradiction: Read correctly skips the newline for command records in text format, while Tail always writes it, causing inconsistent output on the same stream.

Recommended fix

Apply the same skip-newline logic used by Read to Tail:

write_record(&record, &mut writer, args.format).await?;
let skip_newline = matches!(args.format, RecordFormat::Text)
    && record.is_command_record();  // <-- FIX 🟒 Add skip-newline check for command records in text
if !skip_newline {
    writer
        .write_all(b"\n")
        .await
        .map_err(|e| CliError::RecordWrite(e.to_string()))?;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions