# Buggy code
def safe_run(self, manifest):
    catchable_errors = (
        dbt.exceptions.CompilationException,
        dbt.exceptions.RuntimeException,
    )

    result = RunModelResult(self.node)
    started = time.time()
    exc_info = (None, None, None)

    try:
        # if we fail here, we still have a compiled node to return
        # this has the benefit of showing a build path for the errant model
        compiled_node = self.compile(manifest)
        result.node = compiled_node

        # for ephemeral nodes, we only want to compile, not run
        if not self.is_ephemeral_model(self.node):
            result = self.run(compiled_node, manifest)

    except catchable_errors as e:
        if e.node is None:
            e.node = result.node

        result.error = dbt.compat.to_string(e)
        result.status = "ERROR"

    except dbt.exceptions.InternalException as e:
        build_path = self.node.build_path
        prefix = "Internal error executing {}".format(build_path)

        error = "{prefix}\n{error}\n\n{note}".format(
            prefix=dbt.ui.printer.red(prefix),
            error=str(e).strip(),
            note=INTERNAL_ERROR_STRING,
        )
        logger.debug(error)

        result.error = dbt.compat.to_string(e)
        result.status = "ERROR"

    except Exception as e:
        # set this here instead of finally, as python 2/3 exc_info()
        # behavior with re-raised exceptions are slightly different
        exc_info = sys.exc_info()
        prefix = "Unhandled error while executing {filepath}".format(
            filepath=self.node.build_path
        )

        error = "{prefix}\n{error}".format(
            prefix=dbt.ui.printer.red(prefix), error=str(e).strip()
        )

        logger.error(error)
        raise e

    finally:
        exc_str = self._safe_release_connection()

        # if we had an unhandled exception, re-raise it
        if exc_info and exc_info[1]:
            six.reraise(*exc_info)

        # if releasing failed and the result doesn't have an error yet, set
        # an error
        if exc_str is not None and result.error is None:
            result.error = exc_str
            result.status = "ERROR"

    result.execution_time = time.time() - started
    return result


# Traceback (dbt.exceptions.DependencyException)
$ cat macros/oops_materialization.sql
{% materialization oops, default -%}
{{ exceptions.raise_dependency_error('x') }}
{%- endmaterialization %}
$ cat models/base.sql
{{ config(materialized='oops') }}
select 1 as id
$ cat models/dependent.sql
select * from {{ ref('base') }}
$ cat dbt_project.yml
name: 'debug'
version: '0.1.0'
profile: 'debug'
$ dbt run
Found 2 models, 0 tests, 0 archives, 0 analyses, 96 macros, 2 operations, 0 seed files

14:33:08 | Concurrency: 2 threads (target='default')
14:33:08 |
14:33:08 | 1 of 2 START oops model dbt_postgres.base............................ [RUN]
Exception in thread Thread-1:
Traceback (most recent call last):
File "/Users/jake/.pyenv/versions/3.6.5/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/Users/jake/.pyenv/versions/3.6.5/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "/Users/jake/.pyenv/versions/3.6.5/lib/python3.6/multiprocessing/pool.py", line 119, in worker
result = (True, func(*args, **kwds))
File "/Users/jake/src/fishtown/dbt/dbt/runner.py", line 80, in call_runner
result = runner.safe_run(self.manifest)
File "/Users/jake/src/fishtown/dbt/dbt/node_runners.py", line 90, in safe_run
result = self.run(compiled_node, manifest)
File "/Users/jake/src/fishtown/dbt/dbt/node_runners.py", line 159, in run
return self.execute(compiled_node, manifest)
File "/Users/jake/src/fishtown/dbt/dbt/node_runners.py", line 456, in execute
materialization_macro.generator(context)()
File "/Users/jake/src/fishtown/dbt/dbt/clients/jinja.py", line 102, in call
return macro(*args, **kwargs)
File "/Users/jake/.pyenv/versions/3.6.5/envs/dbt36old/lib/python3.6/site-packages/jinja2/runtime.py", line 575, in __call__
return self._invoke(arguments, autoescape)
File "/Users/jake/.pyenv/versions/3.6.5/envs/dbt36old/lib/python3.6/site-packages/jinja2/asyncsupport.py", line 110, in _invoke
return original_invoke(self, arguments, autoescape)
File "/Users/jake/.pyenv/versions/3.6.5/envs/dbt36old/lib/python3.6/site-packages/jinja2/runtime.py", line 579, in _invoke
rv = self._func(*arguments)
File "dbt-a42f706d92e89d13921e04ab", line 16, in macro
to_string(environment.call(context, environment.getattr((undefined(name='exceptions') if l_1_exceptions is missing else l_1_exceptions), 'raise_dependency_error'), 'x')),
File "/Users/jake/.pyenv/versions/3.6.5/envs/dbt36old/lib/python3.6/site-packages/jinja2/sandbox.py", line 427, in call
return __context.call(__obj, *args, **kwargs)
File "/Users/jake/.pyenv/versions/3.6.5/envs/dbt36old/lib/python3.6/site-packages/jinja2/runtime.py", line 262, in call
return __obj(*args, **kwargs)
File "/Users/jake/src/fishtown/dbt/dbt/exceptions.py", line 218, in raise_dependency_error
raise DependencyException(msg)
dbt.exceptions.DependencyException: x

^C
Exited because of keyboard interrupt.

Done. PASS=0 ERROR=0 SKIP=0 TOTAL=0
ctrl-c

# Fixed code
def safe_run(self, manifest):
    catchable_errors = (
        dbt.exceptions.CompilationException,
        dbt.exceptions.RuntimeException,
    )

    result = RunModelResult(self.node)
    started = time.time()

    try:
        # if we fail here, we still have a compiled node to return
        # this has the benefit of showing a build path for the errant model
        compiled_node = self.compile(manifest)
        result.node = compiled_node

        # for ephemeral nodes, we only want to compile, not run
        if not self.is_ephemeral_model(self.node):
            result = self.run(compiled_node, manifest)

    except catchable_errors as e:
        if e.node is None:
            e.node = result.node

        result.error = dbt.compat.to_string(e)
        result.status = "ERROR"

    except dbt.exceptions.InternalException as e:
        build_path = self.node.build_path
        prefix = "Internal error executing {}".format(build_path)

        error = "{prefix}\n{error}\n\n{note}".format(
            prefix=dbt.ui.printer.red(prefix),
            error=str(e).strip(),
            note=INTERNAL_ERROR_STRING,
        )
        logger.debug(error)

        result.error = dbt.compat.to_string(e)
        result.status = "ERROR"

    except Exception as e:
        prefix = "Unhandled error while executing {filepath}".format(
            filepath=self.node.build_path
        )

        error = "{prefix}\n{error}".format(
            prefix=dbt.ui.printer.red(prefix), error=str(e).strip()
        )

        logger.error(error)
        result.error = dbt.compat.to_string(e)
        result.status = "ERROR"

    finally:
        exc_str = self._safe_release_connection()

        # if releasing failed and the result doesn't have an error yet, set
        # an error
        if exc_str is not None and result.error is None:
            result.error = exc_str
            result.status = "ERROR"

    result.execution_time = time.time() - started
    return result