cookiecutter / cookiecutter/cookiecutter
[Bug]: Files starting with PACKAGE_NAME := ... misclassified as binary and skipped
- Dominant language
- Python
- Stars
- 25.1k
- Forks
- 2.3k
- PR merge metrics
- No merged PRs in 30d
Description
### What happened?
## Summary
Cookiecutter incorrectly classifies certain plain text template files as binary when they start with:
```
PACKAGE_NAME := {{ cookiecutter.project_slug }}
```
As a result, the file is copied without Jinja rendering, leaving template variables unresolved.
---
## Expected Behavior
The file should be treated as text and rendered:
```
PACKAGE_NAME := example-package
```
---
## Actual Behavior
The file is treated as binary and copied as-is:
```
PACKAGE_NAME := {{ cookiecutter.project_slug }}
```
No warning or error is emitted.
---
## Root Cause
Cookiecutter uses the `binaryornot` library to determine whether a file is binary.
The file starts with:
```
PACKAGE_NAME := ...
```
The first bytes are:
```
PACK
```
`binaryornot` includes `b"PACK"` in its binary signature list, so:
```
chunk.startswith(b"PACK") → True
```
This results in a false positive binary classification, causing Cookiecutter to skip rendering entirely.
---
## Debug Evidence
```python
from binaryornot.check import is_binary
print(is_binary("foo_file")) # True
```
```python
from binaryornot.helpers import _BINARY_SIGNATURES
chunk = b'PACKAGE_NAME := {{ cookiecutter.project_slug }}\n'
for sig in _BINARY_SIGNATURES:
if chunk.startswith(sig):
print(sig)
```
**Output:**
```
b'PACK'
```
## Workarounds
Any of the following avoids the issue:
- Add a blank line
```
PACKAGE_NAME := {{ cookiecutter.project_slug }}
```
- Add a comment
```
# Generated by cookiecutter
PACKAGE_NAME := {{ cookiecutter.project_slug }}
```
- Rename variable
```
PROJCT_NAME := {{ cookiecutter.project_slug }}
```
## 💡 Proposed Enhancement
Since binary detection is heuristic and can produce false positives, it would be useful to provide a way to override it.
### Proposal
Add a configuration option similar to `_copy_without_render`, for example:
```json
{
"_force_render": [
"Makefile",
"foo_file",
"*.mk"
]
}
```
### Suggested Behavior
1. If path matches `_copy_without_render` → copy without rendering
2. If path matches `_force_render` → always render as text
3. Otherwise → use binary detection
---
## Benefits
- Avoids fragile reliance on heuristics
- Gives template authors explicit control
- Solves real-world edge cases like this one
- Maintains backward compatibility
---
## Impact
- Silent rendering failures
- Hard to debug without `--verbose`
- Affects common files (e.g., Makefile, config files)
- Any file starting with `PACK...` may be impacted
---
## Conclusion
This is a false positive in binary detection leading to skipped rendering. A configurable override would make Cookiecutter more robust and predictable.
### Steps to reproduce
1. Create a minimal template:
```json
cookiecutter.json
{
"project_slug": "example-package"
}
```
```
{{cookiecutter.project_slug}}/foo_file
PACKAGE_NAME := {{ cookiecutter.project_slug }}
```
2. Run:
```
cookiecutter --no-input /path/to/template
```
3. Inspect output:
```
cat example-package/foo_file
```
### Template
cookiecutter.json
```
{
"project_slug": "example-package"
}
```
### Output / traceback
```shell
```
### Cookiecutter version
2.7.1
### Python version
3.12
### Operating system
Linux
### How did you install Cookiecutter?
uv
### Additional context
_No response_
Contributor guide
Research direction
Start by reproducing the issue with `cookiecutter --no-input` and inspect `binaryornot.check.is_binary` together with `_BINARY_SIGNATURES`. Trace how binary detection affects template rendering, then verify that the `PACKAGE_NAME := {{ cookiecutter.project_slug }}` file renders as `PACKAGE_NAME := example-package` while existing binary-copy behavior remains intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100