[Shell] Help us improve #cz2 Handle invalid commands

Hey @zbeers, it looks like the issue was caused by an extra \n, since print already appends a newline by default:

After each command, the tester will check if <command_name>: command not found is printed, and whether a prompt is printed for the next command.

This is very confusing, the solution will result in command not found for anything input, so why not state that any command input will not be found, because we havent created any commands yet? I understand you are building up to it, but still the instructions are confusing.

Hi @SoundBoySelecta, thanks for sharing your feedback!

Could you confirm if you’re referring to #cz2 Handle invalid commands or #ff0 REPL?

Actually, we do mention “all commands [are] invalid” in both stages:

That said, I agree that the instructions should be clearer in setting the right expectations upfront.

Hi @andy1li ,
Surprisingly, I ran into some unexpected trouble with this part of the exercise. However, I finally figured out why!

When building a shell, there are multiple ways to capture input:

  1. os.Stdin
  2. /dev/tty

Most real-world shells (Bash, Zsh, Fish, etc.) always read from /dev/tty, not stdin, to ensure:

  • The shell remains interactive even if stdin is redirected (e.g., when running in a pipeline).
  1. The shell can handle password prompts, interactive input, and terminal controls properly.
  2. The shell still works when running scripts while keeping user interaction possible.

So, I updated my code to read from /dev/tty, thinking this was the correct thing to do. However, I think your tests send input via stdin. This caused my implementaiton to fail despite working fine when manually running the shell locally.

Would it be possible to adjust the tests to accommodate /dev/tty as well? That would be amazing!

I really appreciate the effort you guys have put into making such an awesome product!

Thanks again! :blush:
Durga

1 Like

Hey @dgiridharan,
Thanks for the kind words!

In our tester, we internally use a pty to handle the communication between the tester and the user’s shell.

You can refer to the code here for more details.

I tried hacking around to make reads using go-tty work, but came up empty handed.

However, I managed to create a handle to /dev/tty and successfully read from it. This approach seems to work.

// New initializes a new Shell instance and returns in.
func New() (*Shell, error) {
	var input io.Reader

	ttyFile, err := os.Open("/dev/tty")
	if err == nil {
		input = ttyFile
	} else {
		// Fall back to stdin
		input = os.Stdin
	}

	shell := Shell{
		running:   false,
		readIn:    input,
		writerOut: os.Stdout,
	}

	return &shell, nil
}
1 Like

Hi @ryan-gang,
Amazing! Thanks for the quick response and you’re absolutely right. I was able to get the tests passing using the following example:

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	out := os.Stdout
	in, err := os.Open("/dev/tty")
	if err != nil {
		_, _ = fmt.Fprintf(out, "Error: %s\n", err)
		os.Exit(1)
	}
	defer in.Close()

	reader := bufio.NewReader(in)

	fmt.Fprint(out, "$ ")

	input, err := reader.ReadString('\n')
	if err != nil {
		_, _ = fmt.Fprintf(out, "Error: %s\n", err)
		os.Exit(1)
	}

	fmt.Fprintf(out, "%s: command not found\n", input[0:len(input)-1])
}

I was actually trying to use the go-tty library instead. Here’s a quick example using it:

package main

import (
	"fmt"
	"os"

	"github.com/mattn/go-tty"
)

func main() {
	out := os.Stdout
	tty, err := tty.Open()
	if err != nil {
		_, _ = fmt.Fprintf(out, "%s\n", err)
		os.Exit(1)
	}
	defer tty.Close()

	fmt.Fprint(out, "$ ")

	input, err := tty.ReadString()
	if err != nil {
		_, _ = fmt.Fprintf(out, "%s\n", err)
		os.Exit(1)
	}

	fmt.Fprintf(out, "%s: command not found\n", input)
}

When running this in the command line, it correctly prints proper output:

However, when running the test, I get this:

Initiating test run...
⏳ Turbo test runners busy. You are in queue.
Upgrade to skip the wait: https://codecrafters.io/turbo
Running tests. Logs should appear shortly...
[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.
Debug = true
[tester::#OO8] Running tests for Stage #OO8 (Print a prompt)
[tester::#OO8] Running ./your_program.sh
[your-program] $ 
[tester::#OO8] ✓ Received prompt
[tester::#OO8] Test passed.
[tester::#CZ2] Running tests for Stage #CZ2 (Handle invalid commands)
[tester::#CZ2] Running ./your_program.sh
[your-program] $ invalid_pear_command
[tester::#CZ2] Output does not match expected value.
[tester::#CZ2] Expected: "invalid_pear_command: command not found"
[tester::#CZ2] Received: ""
[tester::#CZ2] Assertion failed.
[tester::#CZ2] Test failed
View our article on debugging test failures: https://codecrafters.io/debug

So it seems to be a library-specific issue. I was hoping to use go-tty since it would simplify things quite a bit! It’s really unclear why this would is happening as the library does exactly what I did in my previous working example (but just abstracts everything).

Sorry for the trouble, and thanks again for the quick reply! I really appreciate it. You guys have built an incredible product, and I’m having a blast coding while learning a new language. :blush:

Cheers!

2 Likes

Hi everyone ! I have a probleme while excuting my project in handle invalid commands. There is a problem of java versions.

Hey @kaloumbi, our tester uses Java 23, so the issue could be resolved by ensuring your environment has the same version.

Let me know if you need any help!

With Zig when testing locally in windows, readUntilDelimiter includes a ‘\r’ at the end of the read string, which prevented string from printing correctly until it was excluded. This made it much harder to debug than I think was intended for the exercise.

1 Like

Please, make possible complete this whole challenge using Gradle for Koltin.
Not Maven, but Gradle. Maven makes me depressed. It is an old XML …

2 Likes

The behavior of provided snippet will vary, I guess, in windows OS as it adds carriage return (\r), ASCII character - 13 along with \n when ‘Enter’ is pressed

Code snippet -

Output is added in reply

1 Like

Output -