Repository: dbt-labs/dbt-core

Issue:
[Bug] query-comments aren't appended when a Manifest is passed to dbtRunner
### Is this a new bug in dbt-core?

- [X] I believe this is a new bug in dbt-core
- [X] I have searched the existing issues, and I could not find an existing issue for this bug

### Current Behavior

The IDE uses the dbtRunner as an entry point for dbt invocations. One common flow is to get the manifest obj returned after calling a parse as a part of the dbtRunnerResult, then pass it back to the runner obj for subsequent commands until the user makes a change to their project. We have observed that passing a Manifest object to the dbtRunner results in the removal of expected query-comments. We confirmed the comments are in the sql as expected as long as dbtRunner is instantiated without a cached manifest.

### Expected Behavior

If a query-comment is defined, `sql` returned from dbt invocations could contain those comments regardless of how the dbtRunner is invoked

### Steps To Reproduce

Here are reliable repro steps from a python repl (this is using my locally installed dbt 1.7.7).

1. Add the following to a local dbt project's dbt_project.yml:
```
query-comment:
  comment: "comment {{ dbt_version }}"
  append: true
```
2. Start a python repl on a dbt project in a venv with dbt installed and import the dbtRunner 
```
(dbt-cloud-env) racheldaniel:jaffle_shop/ (master✗) $ python3
[12:40:18] Python 3.8.10 (default, Nov 29 2023, 10:42:27)
>>> from dbt.cli.main import dbtRunner
```

3. Create an instance without a manifest and run a build
```
>>> dbt = dbtRunner()
>>> dbt.invoke(["--log-format=json", "--log-level=debug", "build", "--select", "stg_customers"])
```
4.  Search for your comment in the logs, it should be present (log truncated):
```
{"data": {"conn_name": "model.jaffle_shop.stg_customers", "node_info": {"materialized": "view", ... "sql": "create or replace  ... nselect * from renamed\n  )\n/* comment 1.7.7 */;", "name": "SQLQuery", "pid": 34081, "thread": "Thread-1", "ts": "2024-02-08T18:43:37.207812Z"}}
```
5.  Run a parse and store the result:
```
>>> result = dbt.invoke(["--log-format=json", "--log-level=debug", "parse"])
```
6. Reinstantiate the dbtRunner using the manifest in that result, then call the same build:
```
>>> dbt = dbtRunner(manifest=result.result)
>>> dbt.invoke(["--log-format=json", "--log-level=debug", "build", "--select", "stg_customers"])
```
7. Observe the query comment is now missing (log truncated):
```
{"data": {"conn_name": "model.jaffle_shop.stg_customers", "node_info": {"materialized": "view", ... "sql": "create or replace   view DEVELOPMENT.dbt_dev_rdaniel_tests.stg_customers\n  \n   as (\n    with source as (\n    select * from DEVELOPMENT.dbt_dev_rdaniel_tests.raw_customers\n\n),\n\nrenamed as (\n\n    select\n        id as customer_id,\n        first_name,\n        last_name\n\n    from source\n\n)\n\nselect * from renamed\n  );"}...}
```

### Relevant log output

_No response_

### Environment

```markdown
- OS: Sonoma 14.1
- Python: 3.8.10
- dbt: 1.7.7 (observed behavior in 1.6.0+, did not test with dbtRunner in 1.5 as IDE uses rpc for <=1.5)
```


### Which database adapter are you using with dbt?

snowflake

### Additional Context

Experience team ticket with historical context -- https://dbtlabs.atlassian.net/browse/XP-2671



Discussion:
Thanks for such a nice write-up @racheldaniel -- it allowed me to reproduce the same thing that you are seeing.

Resolving patch:
diff --git a/core/dbt/cli/requires.py b/core/dbt/cli/requires.py
--- a/core/dbt/cli/requires.py
+++ b/core/dbt/cli/requires.py
@@ -15,6 +15,7 @@
 from dbt.cli.flags import Flags
 from dbt.config import RuntimeConfig
 from dbt.config.runtime import load_project, load_profile, UnsetProfile
+from dbt.context.manifest import generate_query_header_context
 
 from dbt_common.events.base_types import EventLevel
 from dbt_common.events.functions import (
@@ -290,6 +291,10 @@ def wrapper(*args, **kwargs):
                 adapter = get_adapter(runtime_config)
                 adapter.set_macro_context_generator(generate_runtime_macro_context)
                 adapter.set_macro_resolver(ctx.obj["manifest"])
+                query_header_context = generate_query_header_context(
+                    adapter.config, ctx.obj["manifest"]
+                )
+                adapter.connections.set_query_header(query_header_context)
             return func(*args, **kwargs)
 
         return update_wrapper(wrapper, func)


Test patch:
diff --git a/tests/functional/dbt_runner/test_dbt_runner.py b/tests/functional/dbt_runner/test_dbt_runner.py
--- a/tests/functional/dbt_runner/test_dbt_runner.py
+++ b/tests/functional/dbt_runner/test_dbt_runner.py
@@ -6,6 +6,8 @@
 from dbt.cli.main import dbtRunner
 from dbt.exceptions import DbtProjectError
 from dbt.adapters.factory import reset_adapters, FACTORY
+from dbt.tests.util import read_file, write_file
+from dbt.version import __version__ as dbt_version
 
 
 class TestDbtRunner:
@@ -90,3 +92,31 @@ def test_pass_in_manifest(self, project, dbt):
         # Check that the adapters are registered again.
         assert result.success
         assert len(FACTORY.adapters) == 1
+
+
+class TestDbtRunnerQueryComments:
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "models.sql": "select 1 as id",
+        }
+
+    @pytest.fixture(scope="class")
+    def project_config_update(self):
+        return {
+            "query-comment": {
+                "comment": f"comment: {dbt_version}",
+                "append": True,
+            }
+        }
+
+    def test_query_comment_saved_manifest(self, project, logs_dir):
+        dbt = dbtRunner()
+        dbt.invoke(["build", "--select", "models"])
+        result = dbt.invoke(["parse"])
+        write_file("", logs_dir, "dbt.log")
+        # pass in manifest from parse command
+        dbt = dbtRunner(result.result)
+        dbt.invoke(["build", "--select", "models"])
+        log_file = read_file(logs_dir, "dbt.log")
+        assert f"comment: {dbt_version}" in log_file