# Buggy code
def __main(comparewith, directory, config):
    base_directory, config = load_config_from_options(directory, config)

    try:
        files_changed = (
            _run(
                ["git", "diff", "--name-only", comparewith + "..."], cwd=base_directory
            )
            .decode(getattr(sys.stdout, "encoding", "utf8"))
            .strip()
        )
    except CalledProcessError as e:
        click.echo("git produced output while failing:")
        click.echo(e.output)
        raise

    if not files_changed:
        click.echo("On trunk, or no diffs, so no newsfragment required.")
        sys.exit(0)

    files = {
        os.path.normpath(os.path.join(base_directory, path))
        for path in files_changed.strip().splitlines()
    }

    click.echo("Looking at these files:")
    click.echo("----")
    for n, change in enumerate(files, start=1):
        click.echo("{}. {}".format(n, change))
    click.echo("----")

    fragments = set()

    if config.get("directory"):
        fragment_base_directory = os.path.abspath(config["directory"])
        fragment_directory = None
    else:
        fragment_base_directory = os.path.abspath(
            os.path.join(base_directory, config["package_dir"], config["package"])
        )
        fragment_directory = "newsfragments"

    fragments = {
        os.path.normpath(path)
        for path in find_fragments(
            fragment_base_directory,
            config["sections"],
            fragment_directory,
            config["types"],
        )[1]
    }
    fragments_in_branch = fragments & files

    if not fragments_in_branch:
        click.echo("No new newsfragments found on this branch.")
        sys.exit(1)
    else:
        click.echo("Found:")
        for n, fragment in enumerate(fragments_in_branch, start=1):
            click.echo("{}. {}".format(n, fragment))
        sys.exit(0)


# Traceback (TypeError)
Traceback (most recent call last):
File "/opt/hostedtoolcache/Python/2.7.17/x64/lib/python2.7/runpy.py", line 174, in _run_module_as_main
"__main__", fname, loader, pkg_name)
File "/opt/hostedtoolcache/Python/2.7.17/x64/lib/python2.7/runpy.py", line 72, in _run_code
exec code in run_globals
File "/home/runner/work/tahoe-lafs/tahoe-lafs/.tox/codechecks/lib/python2.7/site-packages/towncrier/check.py", line 98, in <module>
_main()
File "/home/runner/work/tahoe-lafs/tahoe-lafs/.tox/codechecks/lib/python2.7/site-packages/click/core.py", line 764, in __call__
return self.main(*args, **kwargs)
File "/home/runner/work/tahoe-lafs/tahoe-lafs/.tox/codechecks/lib/python2.7/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/home/runner/work/tahoe-lafs/tahoe-lafs/.tox/codechecks/lib/python2.7/site-packages/click/core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/runner/work/tahoe-lafs/tahoe-lafs/.tox/codechecks/lib/python2.7/site-packages/click/core.py", line 555, in invoke
return callback(*args, **kwargs)
File "/home/runner/work/tahoe-lafs/tahoe-lafs/.tox/codechecks/lib/python2.7/site-packages/towncrier/check.py", line 31, in _main
return __main(compare_with, directory, pyproject)
File "/home/runner/work/tahoe-lafs/tahoe-lafs/.tox/codechecks/lib/python2.7/site-packages/towncrier/check.py", line 44, in __main
.decode(getattr(sys.stdout, "encoding", "utf8"))
TypeError: decode() argument 1 must be string, not None

# Fixed code
def __main(comparewith, directory, config):
    base_directory, config = load_config_from_options(directory, config)

    # Use UTF-8 both when sys.stdout does not have .encoding (Python 2.7) and
    # when the attribute is present but set to None (explicitly piped output
    # and also some CI such as GitHub Actions).
    encoding = getattr(sys.stdout, "encoding", None)
    if encoding is None:
        encoding = "utf8"

    try:
        files_changed = (
            _run(
                ["git", "diff", "--name-only", comparewith + "..."], cwd=base_directory
            )
            .decode(encoding)
            .strip()
        )
    except CalledProcessError as e:
        click.echo("git produced output while failing:")
        click.echo(e.output)
        raise

    if not files_changed:
        click.echo("On trunk, or no diffs, so no newsfragment required.")
        sys.exit(0)

    files = {
        os.path.normpath(os.path.join(base_directory, path))
        for path in files_changed.strip().splitlines()
    }

    click.echo("Looking at these files:")
    click.echo("----")
    for n, change in enumerate(files, start=1):
        click.echo("{}. {}".format(n, change))
    click.echo("----")

    fragments = set()

    if config.get("directory"):
        fragment_base_directory = os.path.abspath(config["directory"])
        fragment_directory = None
    else:
        fragment_base_directory = os.path.abspath(
            os.path.join(base_directory, config["package_dir"], config["package"])
        )
        fragment_directory = "newsfragments"

    fragments = {
        os.path.normpath(path)
        for path in find_fragments(
            fragment_base_directory,
            config["sections"],
            fragment_directory,
            config["types"],
        )[1]
    }
    fragments_in_branch = fragments & files

    if not fragments_in_branch:
        click.echo("No new newsfragments found on this branch.")
        sys.exit(1)
    else:
        click.echo("Found:")
        for n, fragment in enumerate(fragments_in_branch, start=1):
            click.echo("{}. {}".format(n, fragment))
        sys.exit(0)