Every OnEntry( MethodExecutionArgs args ) you have ever written pays three costs on every call: it allocates an object on the heap, it boxes the arguments and the return value into object, and it makes you cast them back. Our own Pattern Libraries stopped paying those costs a long time ago, because they use a mechanism we kept internal: advice parameter binding. With PostSharp 2027.0, this mechanism is public, documented and checked at build time.
Here is a typical logging aspect written as a simple aspect. It logs the first argument, the return value, the exception and the duration of the method:
[PSerializable]
public sealed class LogAttribute : OnMethodBoundaryAspect
{
public override void OnEntry( MethodExecutionArgs args )
{
Console.WriteLine( "Entering {0}({1}).", args.Method.Name, args.Arguments[0] );
args.MethodExecutionTag = Stopwatch.StartNew();
}
public override void OnSuccess( MethodExecutionArgs args )
{
Console.WriteLine( "{0} returned {1}.", args.Method.Name, args.ReturnValue );
}
public override void OnException( MethodExecutionArgs args )
{
Console.WriteLine( "{0} failed: {1}", args.Method.Name, args.Exception.Message );
}
public override void OnExit( MethodExecutionArgs args )
{
var stopwatch = (Stopwatch) args.MethodExecutionTag;
Console.WriteLine( "{0} took {1} ms.", args.Method.Name, stopwatch.ElapsedMilliseconds );
}
}
It works, and it has worked for many years. But look at what it asks of the runtime:
MethodExecutionArgs lives on the heap, and so does the Arguments collection.Arguments collection stores the arguments with their own types, but exposes them as object, so reading an int through args.Arguments[0] boxes it. args.ReturnValue is an object too, so an int return value is boxed as well.MethodExecutionTag is an object too, so the stopwatch must be cast back, and a wrong cast fails at run time.On a method called a few times per request, nobody notices. On a hot path, the aspect becomes the most expensive part of the method.
With advice parameter binding, the aspect derives from MethodLevelAspect, and each advice method declares only the values it needs, as typed parameters. A binding attribute on each parameter says where the value comes from:
[PSerializable]
public sealed class LogAttribute : MethodLevelAspect
{
[OnMethodEntryAdvice]
public void OnEntry( [DeclarationName] string methodName,
[Argument( 0 )] object argument,
[State( StateScope.MethodInvocation )] out Stopwatch stopwatch )
{
Console.WriteLine( "Entering {0}({1}).", methodName, argument );
stopwatch = Stopwatch.StartNew();
}
[OnMethodSuccessAdvice]
public void OnSuccess<T>( [DeclarationName] string methodName, [ReturnValue] T returnValue )
{
Console.WriteLine( "{0} returned {1}.", methodName, returnValue );
}
[OnMethodExceptionAdvice]
public void OnException( [DeclarationName] string methodName, [Exception] Exception exception )
{
Console.WriteLine( "{0} failed: {1}", methodName, exception.Message );
}
[OnMethodExitAdvice]
public void OnExit( [DeclarationName] string methodName,
[State( StateScope.MethodInvocation )] Stopwatch stopwatch )
{
Console.WriteLine( "{0} took {1} ms.", methodName, stopwatch.ElapsedMilliseconds );
}
}
The bindings in this example:
[DeclarationName] gives the name of the method. It is a constant, computed at build time, so there is no reflection call at run time.[State] declares a typed variable shared by the advices for one execution of the method. The entry advice assigns it with out, and the exit advice reads it. There is no MethodExecutionTag and no cast.[ReturnValue] on a generic parameter T receives the return value without boxing. PostSharp infers T for each target method.[Argument( 0 )] reads the first argument. Here it is still boxed, because the parameter is object. Make the advice generic to avoid it, as shown below.[Exception] gives the exception, typed as Exception.The aspect is applied exactly as before, with [Log] on a method or with multicasting.
Bound parameters are a build-time contract, so the woven code passes the values directly. Take a smaller profiling aspect, with an entry advice that starts a [State] stopwatch and an exit advice that prints the [DeclarationName] and the duration (see Aspect Development Concepts). PostSharp transforms the target method into code equivalent to this:
public void PlaceOrder( Order order )
{
Stopwatch stopwatch;
aspect.OnEntry( out stopwatch );
try
{
// Method body.
}
finally
{
aspect.OnExit( "ProfileAspect.OrderService.PlaceOrder", stopwatch );
}
}
The state is a local variable, the method name is a string literal, and the advices are plain method calls. Apart from the Stopwatch itself, the woven code allocates nothing.
An advice method can be generic. PostSharp infers the type arguments separately for each target method, with rules similar to the type inference of the C# compiler, and then calls a different instantiation of the advice from each target.
Take the OnSuccess<T> advice above. On int Square( int value ), PostSharp calls OnSuccess<int>. On string Format( Order order ), it calls OnSuccess<string>. The same source method serves every signature, and an int stays an int all the way.
Inference also works through constructed types and constraints:
[OnMethodEntryAdvice]
public void OnEntry<T>( [DeclarationName] string methodName,
[Argument( 0 )] IEnumerable<T> items )
where T : IComparable<T>
{
Console.WriteLine( "{0} received {1} items, the first is {2}.",
methodName, items.Count(), items.FirstOrDefault() );
}
Applied to a method that takes a List<int>, this advice gets T = int: PostSharp matches IEnumerable<T> against the type of the argument, its base types and its interfaces, as C# would. The rules, in short:
ref, out or in parameter receives the location itself.OnEntry<T>( [Argument( 0 )] T a, [Argument( 1 )] T b ) on M( string, object ) gives T = object.T[], T?, IList<T> and the like are matched against the value’s type, its base types and its interfaces.unmanaged, new() and constraints that refer to other type parameters.When a type argument cannot be inferred for a target, or violates a constraint, the build fails with the error LA0220, which states the reason. Apply the advice above to a method that takes a List<object>, and you learn at build time that object does not implement IComparable<object>.
The type of a parameter can also use the type parameters of a generic aspect class. They are replaced by the type arguments of the aspect instance. See Generic advice methods for the complete rules.
A simple aspect accepts any method, then fails at run time if it makes a wrong assumption, for instance by reading args.Arguments[0] on a method that has no parameters.
With bound parameters, PostSharp checks each advice against each target when it weaves the aspect. An advice that does not fit a target is a build error with a message code. Apply the LogAttribute above to a method without parameters, and [Argument( 0 )] causes the error LA0225: the target method has no parameter at this index. A parameter of the wrong type, a binding used in an advice that does not support it, or a type argument that cannot be inferred all have their own codes. PostSharp reports all the errors of an aspect in one build.
The complete list of build messages is in the reference.
The logging example uses five bindings. The Advice Parameter Reference lists them all. Among them:
ref and out. Replace an argument, set or replace the return value, or replace the exception.[FlowBehavior] to return without executing the method, or to swallow an exception.[CurrentTask] gives the task that the caller observes, including for ValueTask methods, and [AsyncCallId] identifies one execution of the async method.[Binding] gives an object that invokes the original method, field or property.[This], [Declaration] and [DeclarationIdentifier] for the instance and the reflection object of the target.Grouping also got simpler. [SelfPointcut] and the Master property are optional when the grouping of the advices is unambiguous, so an aspect that applies to a single method, field, property or event needs neither.
Not necessarily. Simple aspects remain fully supported, and you can keep the AdviceArgs signature in the following cases:
OnInvokeAsync. These advices do not accept bound parameters yet.MethodExecutionArgs.YieldValue gives.The other limitations are listed in the reference.
For the aspects that do run on hot paths, the conversion is mechanical. Change the base class to MethodLevelAspect or LocationLevelAspect, turn each override into an advice method with the matching attribute, replace each args member with a bound parameter, and build. PostSharp tells you what does not fit. The Modernizing Your Custom Aspects article gives the steps and a table that maps each member of MethodExecutionArgs, MethodInterceptionArgs and LocationInterceptionArgs to its binding.
Advice parameter binding is available from PostSharp 2027.0, which also adds support for .NET 11 and C# 15. See What’s New in PostSharp 2027.0 for the full list. Pick one aspect on your hottest path, convert it, and tell us how it went on GitHub.
Happy meta-programming with PostSharp!
This article was first published on a https://postsharp.net/blog under the title Your Aspects, Faster: Advice Parameter Binding in PostSharp 2027.0.

On our road to Wasp 1.0, we are shipping incremental releases, each adding more toward our vision of Wasp as a full-stack JS/TS framework that is liked and useful for humans and agents alike.
Wasp 0.26 is out.
This release lets you run several Wasp apps on one machine at once, so each coding agent can work in its own worktree.
Additionally, each app in a parallel worktree now gets its own set of ports, auth flows are getting safer defaults, the docs structure has been revamped, and wasp show now prints your app as Wasp sees it.
More on these below, so let’s dive in! 🐝
When you run several coding agents like Claude Code or Codex in parallel, a common setup is to give each one its own Git worktree. Until this release, only one of those worktrees could run your app.
That’s because wasp start would always use the fixed ports 3000 and 3001, and wasp start db would always use 5432.
In reality, users ran multiple instances and port clashing was a real issue: wasp start db, for example, would simply give up when 5432 was taken. So now, wasp start picks the first available ports on its own. It’ll start with 3000 and 3001 as before and then work its way up. Similarly, wasp start db will do the same, starting at 5432, and wasp start will then find the port your database ended up on.
This also means that each agent can get its own DB instance and can avoid the hassle of sharing the data.
Once you’ve created a new worktree, to set it up, you can just run wasp install and copy your .env files. If your .env.server has a DATABASE_URL, leave it out, so the worktree gets its own dev database instead of the shared one. Then start the database in one terminal and the app in another:
# Terminal 1: keeps the database running
wasp start db
# Terminal 2
wasp db migrate-dev
wasp start
If you need a fixed port, that’s also possible and you can pass --client-port, --server-port or --db-port values. A special h/t to our community contributor for the last one, @Nihal-Ahamed-MS.
Also, Wasp now manages your dev ports and URLs, so if you set WASP_SERVER_URL or WASP_WEB_CLIENT_URL in .env.server, REACT_APP_API_URL in .env.client, or server.port in vite.config.ts manually, it will now fail.
We explain more and what to remove in our migration guide to 0.26.
And your coding agent can benefit from the same setup - you can just prompt it to start the app in its own worktree and check the changes it’s making there, while your app can stay open and running in another one.
To upgrade, run the following:
npm i -g @wasp.sh/wasp-cli@latest
We also suggest you take a look at our worktree guide and if you get stuck or want to share your experiences with us, drop by our Discord server.
wasp show commandWe’ve seen users having very large Wasp app’s spec files and others have had specs spanning multiple other files making it difficult to see all your routes or queries in one place.
We changed that by introducing wasp show spec command. Running it will now print out your app the same way Wasp sees it - all your pages, routes, entities, actions, queries, APIs, CRUDs and even jobs.
$ wasp show spec
...
Routes
Name Path Destination Loading
LoginRoute /login LoginPage Lazy
RootRoute / MainPage Lazy
SignupRoute /signup SignupPage Lazy
Queries
Name Import Entities
getTasks { getTasks } from "./src/queries" Task
...
If you want to see more information about your last build, you can run wasp show build.
Just remember to run both of these commands in your project folder and if you want the output in a more structured way, just add --json and get it like that.
The new wasp show replaces our older wasp info so if you depend on it and your code gets the value from it, switch it to wasp show build --json and then read the value from lastCompile.waspVersion.
And, again, agents will benefit from this structured output, too. You can quickly ask them which pages require auth or which queries and actions use a specific entity before changing that entity, avoiding conflicts and issues down the road.
You can find both commands in our CLI reference.

We improved our docs structure significantly as well.
In our previous version, 0.25, we had 11 top-level sections like Data Model, Project Setup or Miscellaneous. In 0.26 we regrouped them so it’s faster to find a page when you know what you are looking for.
The structure is now streamlined to 6 sections: Getting started, Tutorial, Features, Advanced, Deployment, and Migration guides.
We also shortened the names of numerous pages - for example, Custom HTTP API Endpoints is now just API routes.
Older links still work, all the newly moved pages redirect correctly, and the older docs keep their structure. Agents will also see the same structure since our llms.txt files follow the new sidebar.
Our users trust Wasp when it comes to auth - we make all those flows for you, so figuring out any issues and polishing some rough edges is our job. In 0.26 we are fixing several of those.
When you reset your password now, the account will be logged out on every device. Password reset will check the token before the new password, so someone without a valid link can’t probe your password rules.
Also, the resend limit for verification emails will now check the time when the last verification email went out, and not the last password reset email.
We also improved how we are handling more global and internationalized addresses like jürgen@münchen.de or those written in UPPERCASE. Additionally, onBeforeSignup now runs before userSignupFields for every supported signup method.
All this ships by default in 0.26 and no changes are needed from your side. The only thing that could possibly break your e2e tests is that email fields now use type="text", so select them with input[name="email"] instead of input[type="email"] if those were part of your testing scripts.
You can read more in our auth docs.
Before we get to the upgrade details: as we said at the top, we're on our road to Wasp 1.0. If you want to know the moment it lands, leave your email below and we'll let you know.
Get an email when Wasp 1.0 lands!
With 0.26, we have introduced some more breaking changes. Our 0.26 migration guide covers most of them, but here are a few for your reference.
wasp compile, wasp start and wasp build stop on TypeScript errors. It checked your code before too, so most apps won't see new errors. If you get any, fix them before you continue.tsconfig.src.json needs an update, and custom Dockerfiles must copy the generated types directory.PORT environment variable.GET / to GET /up. Point your health checks at /up.wasp deploy fly now start with db-, for example --db-vm-size. Fly CLI 0.4.82 and Railway CLI 5.28.0 are the new minimum versions.wasp/server/.... If your app imported one, switch to a documented import such as wasp/server/auth.PATCH and HEAD methods.wasp deploy fly also has new --db-vm-memory, --db-vm-cpus and --db-vm-cpu-kind options.tsconfig.src.json, such as strict, target and lib.Link and NavLink now accept the same search values as routes.X.build, and wasp/client/router exports LinkProps and NavLinkProps.wasp start.src/. Previously, they could fail with "Too many open files".wasp start and wasp db commands that hung in some edge cases.--db-image. Previously, they had no persistent volume. Existing databases are unchanged.morgan package to fix three CVEs. Wasp's usage was not affected.The full list is in the GitHub release notes.
The first time you open an unfamiliar Octopus project (inheriting it, joining a new team, or exploring a corner of your own instance you haven’t touched before), you’re looking at someone else’s decisions with none of the context that went into making them. Real projects pick up channels, triggers, approvals, and a growing list of targets over time.
The pipeline explorer brings a project’s configuration together in one place. It helps you understand how an Octopus project works, on one page.
It’s built for anyone who’s new to a project: joining a team, inheriting ownership, or exploring an unfamiliar corner of their own instance.
It visualizes your deployment pipeline and deployment process as workflow diagrams, showing you how a release gets triggered and how it moves through your environments depending on the channel.
The pipeline explorer presents your project’s deployment pipeline and deployment process as workflow diagrams. Seeing them visually lets you understand how a release is triggered and how it moves through your environments, without piecing that together from separate tabs.

A project’s deployment process depends on settings that live outside its process, like lifecycles, deployment targets, credentials, and approvals. The pipeline explorer surfaces these connections directly, so you don’t need to already know where each one lives.

The pipeline explorer flags conditions that could affect an upcoming deployment directly in the view where you’re already looking.

The pipeline explorer doesn’t replace your existing process, variables, or settings pages. It’s a new view built on top of the same data, not a redesign of how you configure a project. Think of it as a project map.
Open any project and select Pipeline explorer from the navigation to see it for yourself. Next time you visit an unfamiliar project, this is where you’ll want to start.
Read the docs for more details.
Happy deployments!