diff --git a/.chronus/changes/recover-bad-op-is-2024-3-23-17-46-20.md b/.chronus/changes/recover-bad-op-is-2024-3-23-17-46-20.md
new file mode 100644
index 0000000000..553de9ab5d
--- /dev/null
+++ b/.chronus/changes/recover-bad-op-is-2024-3-23-17-46-20.md
@@ -0,0 +1,8 @@
+---
+# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
+changeKind: fix
+packages:
+  - "@typespec/compiler"
+---
+
+Fix compiler crash when using an invalid `is` target in an interface operation template
diff --git a/packages/compiler/src/core/checker.ts b/packages/compiler/src/core/checker.ts
index 7390a622f3..0a57dd3e69 100644
--- a/packages/compiler/src/core/checker.ts
+++ b/packages/compiler/src/core/checker.ts
@@ -1807,7 +1807,7 @@ export function createChecker(program: Program): Checker {
     node: OperationStatementNode,
     mapper: TypeMapper | undefined,
     parentInterface?: Interface
-  ): Operation | ErrorType {
+  ): Operation {
     const inInterface = node.parent?.kind === SyntaxKind.InterfaceStatement;
     const symbol = inInterface ? getSymbolForMember(node) : node.symbol;
     const links = symbol && getSymbolLinks(symbol);
@@ -1819,7 +1819,11 @@ export function createChecker(program: Program): Checker {
     }
 
     if (mapper === undefined && inInterface) {
-      compilerAssert(parentInterface, "Operation in interface should already have been checked.");
+      compilerAssert(
+        parentInterface,
+        "Operation in interface should already have been checked.",
+        node.parent
+      );
     }
     checkTemplateDeclaration(node, mapper);
 
@@ -1837,32 +1841,42 @@ export function createChecker(program: Program): Checker {
     if (node.signature.kind === SyntaxKind.OperationSignatureReference) {
       // Attempt to resolve the operation
       const baseOperation = checkOperationIs(node, node.signature.baseOperation, mapper);
-      if (!baseOperation) {
-        return errorType;
-      }
-      sourceOperation = baseOperation;
-      const parameterModelSym = getOrCreateAugmentedSymbolTable(symbol!.metatypeMembers!).get(
-        "parameters"
-      )!;
-      // Reference the same return type and create the parameters type
-      const clone = initializeClone(baseOperation.parameters, {
-        properties: createRekeyableMap(),
-      });
+      if (baseOperation) {
+        sourceOperation = baseOperation;
+        const parameterModelSym = getOrCreateAugmentedSymbolTable(symbol!.metatypeMembers!).get(
+          "parameters"
+        )!;
+        // Reference the same return type and create the parameters type
+        const clone = initializeClone(baseOperation.parameters, {
+          properties: createRekeyableMap(),
+        });
 
-      clone.properties = createRekeyableMap(
-        Array.from(baseOperation.parameters.properties.entries()).map(([key, prop]) => [
-          key,
-          cloneTypeForSymbol(getMemberSymbol(parameterModelSym, prop.name)!, prop, {
-            model: clone,
-            sourceProperty: prop,
-          }),
-        ])
-      );
-      parameters = finishType(clone);
-      returnType = baseOperation.returnType;
+        clone.properties = createRekeyableMap(
+          Array.from(baseOperation.parameters.properties.entries()).map(([key, prop]) => [
+            key,
+            cloneTypeForSymbol(getMemberSymbol(parameterModelSym, prop.name)!, prop, {
+              model: clone,
+              sourceProperty: prop,
+            }),
+          ])
+        );
+        parameters = finishType(clone);
+        returnType = baseOperation.returnType;
 
-      // Copy decorators from the base operation, inserting the base decorators first
-      decorators = [...baseOperation.decorators];
+        // Copy decorators from the base operation, inserting the base decorators first
+        decorators = [...baseOperation.decorators];
+      } else {
+        // If we can't resolve the signature we return an empty model.
+        parameters = createAndFinishType({
+          kind: "Model",
+          name: "",
+          decorators: [],
+          properties: createRekeyableMap(),
+          derivedModels: [],
+          sourceModels: [],
+        });
+        returnType = voidType;
+      }
     } else {
       parameters = getTypeForNode(node.signature.parameters, mapper) as Model;
       returnType = getTypeForNode(node.signature.returnType, mapper);
@@ -4144,19 +4158,17 @@ export function createChecker(program: Program): Checker {
 
     for (const opNode of node.operations) {
       const opType = checkOperation(opNode, mapper, interfaceType);
-      if (opType.kind === "Operation") {
-        if (ownMembers.has(opType.name)) {
-          reportCheckerDiagnostic(
-            createDiagnostic({
-              code: "interface-duplicate",
-              format: { name: opType.name },
-              target: opNode,
-            })
-          );
-          continue;
-        }
-        ownMembers.set(opType.name, opType);
+      if (ownMembers.has(opType.name)) {
+        reportCheckerDiagnostic(
+          createDiagnostic({
+            code: "interface-duplicate",
+            format: { name: opType.name },
+            target: opNode,
+          })
+        );
+        continue;
       }
+      ownMembers.set(opType.name, opType);
     }
     return ownMembers;
   }
diff --git a/packages/compiler/test/checker/operations.test.ts b/packages/compiler/test/checker/operations.test.ts
index a8d8a65642..fd04995c92 100644
--- a/packages/compiler/test/checker/operations.test.ts
+++ b/packages/compiler/test/checker/operations.test.ts
@@ -259,6 +259,37 @@ describe("compiler: operations", () => {
     ok(gammaTargets.has(newFoo));
   });
 
+  // Regression test for https://github.com/microsoft/typespec/issues/3199
+  it("produce an empty interface operation in template when op is reference is invalid", async () => {
+    testHost.addTypeSpecFile(
+      "main.tsp",
+      `
+      @test op test is IFace.Action<int32>;
+
+      interface IFace {
+        Action<T> is string;
+      }
+      `
+    );
+
+    const [{ test }, diagnostics] = await testHost.compileAndDiagnose("./main.tsp");
+    expectDiagnostics(diagnostics, [
+      {
+        code: "is-operation",
+        message: "Operation can only reuse the signature of another operation.",
+      },
+      {
+        code: "is-operation",
+        message: "Operation can only reuse the signature of another operation.",
+      },
+    ]);
+    strictEqual(test.kind, "Operation");
+    strictEqual(test.parameters.name, "");
+    strictEqual(test.parameters.properties.size, 0);
+    strictEqual(test.returnType.kind, "Intrinsic");
+    strictEqual((test.returnType as IntrinsicType).name, "void");
+  });
+
   it("emit diagnostic when operation is referencing itself as signature", async () => {
     testHost.addTypeSpecFile(
       "main.tsp",
@@ -307,10 +338,6 @@ describe("compiler: operations", () => {
         code: "circular-op-signature",
         message: "Operation 'foo' recursively references itself.",
       },
-      {
-        code: "circular-op-signature",
-        message: "Operation 'bar' recursively references itself.",
-      },
     ]);
   });
 
@@ -330,10 +357,6 @@ describe("compiler: operations", () => {
         code: "circular-op-signature",
         message: "Operation 'foo' recursively references itself.",
       },
-      {
-        code: "circular-op-signature",
-        message: "Operation 'bar' recursively references itself.",
-      },
     ]);
   });