Skip to content

[rush] Include seconds in generated change file names so repeated "rush change" runs do not collide#5818

Open
LeSingh1 wants to merge 2 commits into
microsoft:mainfrom
LeSingh1:fix/change-file-timestamp-seconds
Open

[rush] Include seconds in generated change file names so repeated "rush change" runs do not collide#5818
LeSingh1 wants to merge 2 commits into
microsoft:mainfrom
LeSingh1:fix/change-file-timestamp-seconds

Conversation

@LeSingh1
Copy link
Copy Markdown
Contributor

@LeSingh1 LeSingh1 commented Jun 4, 2026

Fixes #2195

When you run "rush change" twice within the same minute, the second run silently overwrites the change file written by the first one, so one of your messages gets lost. The generated filename only has minute-level granularity, which means both runs resolve to the same path. The issue thread confirms this: waiting a minute between invocations makes it behave correctly.

The cause is in ChangeFile.generatePath(), which called _getTimestamp() with no arguments. That formats the time as YYYY-MM-DD-HH-MM. The method already accepted a useSeconds parameter, but nothing ever passed it. This change passes true so the filename includes seconds (YYYY-MM-DD-HH-MM-SS), which is enough to keep separate invocations from colliding in normal usage.

While turning that on I noticed the useSeconds branch was itself broken. It used matches[2].replace(:, -), which only replaces the first colon, so it produced strings like 20-20:30 with a leftover colon. A colon is not even a valid filename character on Windows. I changed it to a global replace so every colon is converted.

I added a unit test in ChangeFile.test.ts that pins the branch name and the clock, then asserts the generated path contains the seconds and has no leftover colons. I confirmed the test fails against the previous behavior and passes with this change. I also added a change file under common/changes.

Running rush change twice in the same minute silently overwrote the
change file from the first run, since generatePath only used minute
granularity. Pass useSeconds so the filename includes seconds, and fix
the useSeconds branch which only replaced the first colon.

Fixes microsoft#2195
{
"changes": [
{
"comment": "Include seconds in the generated change file name so that running \"rush change\" more than once in the same minute no longer silently overwrites the previously generated change file.",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"comment": "Include seconds in the generated change file name so that running \"rush change\" more than once in the same minute no longer silently overwrites the previously generated change file.",
"comment": "Include seconds in the generated change file name so that running `rush change` more than once in the same minute no longer silently overwrites the previously generated change file.",

}
],
"packageName": "@microsoft/rush",
"email": "sshaurya914@gmail.com"
Copy link
Copy Markdown
Member

@iclanton iclanton Jun 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"email": "sshaurya914@gmail.com"
"email": "LeSingh1@users.noreply.github.com"

@iclanton iclanton enabled auto-merge (squash) June 6, 2026 23:56
@iclanton
Copy link
Copy Markdown
Member

iclanton commented Jun 7, 2026

@LeSingh1 - the build on Windows failed. Can you investigate? May need to use fake timers in Jest.

The Date constructor spy was bypassed by the V8 fast-path on Windows/Node
v24, so new Date().toJSON() returned the real wall-clock time instead of
the pinned value. Switch to jest.useFakeTimers().setSystemTime() which
intercepts at the engine level and is reliable across platforms.

Also fix the not.toContain(':') assertion to check only path.basename()
instead of the full path. On Windows the generated path starts with a
drive letter (e.g. "D:\...") which legitimately contains a colon and
was causing the assertion to fail even though the timestamp itself had
no colons.
auto-merge was automatically disabled June 7, 2026 19:25

Head branch was pushed to by a user without write access

@LeSingh1
Copy link
Copy Markdown
Contributor Author

LeSingh1 commented Jun 7, 2026

Thanks for the pointer. I found two issues in the test that together caused the Windows failure.

The Date constructor spy (jest.spyOn(global, 'Date').mockImplementation(...)) was being bypassed on Windows with Node.js v24. The V8 Date fast-path can skip the spy, so new Date().toJSON() returned the real wall-clock time instead of the pinned value. I switched to jest.useFakeTimers().setSystemTime() with jest.useRealTimers() in the finally block, which intercepts at the engine level and is reliable across all platforms.

The second issue was the not.toContain(':') assertion. On Windows, path.join produces a path starting with a drive letter like D:..., so the full path always contains a colon regardless of the timestamp. I changed the assertion to run on path.basename(generatedPath) only, which is just the filename and has no drive letter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

[rush] rush change --overwrite only effective if run twice in one minute

2 participants